From 7928395641aefd926ce6700ef916c597729a03be Mon Sep 17 00:00:00 2001 From: Oscar Chevalier Date: Sat, 27 Dec 2025 16:37:03 +1100 Subject: [PATCH 01/15] fix: remove material and fix androidx ktx version --- library/Panels/build.gradle.kts | 3 +-- library/Panels/src/main/java/com/bylazar/panels/Panels.kt | 8 +------- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/library/Panels/build.gradle.kts b/library/Panels/build.gradle.kts index e369dbe..eea1a04 100644 --- a/library/Panels/build.gradle.kts +++ b/library/Panels/build.gradle.kts @@ -50,9 +50,8 @@ android { } dependencies { - implementation("androidx.core:core-ktx:1.13.1") + implementation("androidx.core:core-ktx:1.3.0") implementation("androidx.appcompat:appcompat:1.2.0") - implementation("com.google.android.material:material:1.12.0") testImplementation("junit:junit:4.13.2") androidTestImplementation("androidx.test.ext:junit:1.2.1") diff --git a/library/Panels/src/main/java/com/bylazar/panels/Panels.kt b/library/Panels/src/main/java/com/bylazar/panels/Panels.kt index 9f86fae..7acf942 100644 --- a/library/Panels/src/main/java/com/bylazar/panels/Panels.kt +++ b/library/Panels/src/main/java/com/bylazar/panels/Panels.kt @@ -1,19 +1,16 @@ package com.bylazar.panels import android.content.Context -import androidx.core.app.PendingIntentCompat.send -import com.bylazar.panels.core.TextHandler import com.bylazar.panels.core.OpModeHandler import com.bylazar.panels.core.PreferencesHandler +import com.bylazar.panels.core.TextHandler import com.bylazar.panels.plugins.PluginsManager -import com.bylazar.panels.plugins.PluginsManager.contextRef import com.bylazar.panels.reflection.ClassFinder import com.bylazar.panels.server.Socket import com.bylazar.panels.server.StaticServer import com.qualcomm.ftccommon.FtcEventLoop import com.qualcomm.robotcore.eventloop.opmode.OpMode import com.qualcomm.robotcore.eventloop.opmode.OpModeManager -import com.qualcomm.robotcore.eventloop.opmode.OpModeManagerImpl import com.qualcomm.robotcore.eventloop.opmode.OpModeManagerNotifier.Notifications import com.qualcomm.robotcore.eventloop.opmode.OpModeRegistrar import com.qualcomm.robotcore.util.WebHandlerManager @@ -22,10 +19,7 @@ import org.firstinspires.ftc.ftccommon.external.OnCreateEventLoop import org.firstinspires.ftc.ftccommon.external.OnDestroy import org.firstinspires.ftc.ftccommon.external.WebHandlerRegistrar import org.firstinspires.ftc.ftccommon.internal.FtcRobotControllerWatchdogService -import org.firstinspires.ftc.robotcore.internal.opmode.OpModeMeta -import org.firstinspires.ftc.robotcore.internal.opmode.RegisteredOpModes import org.firstinspires.ftc.robotcore.internal.system.AppUtil -import java.lang.ref.WeakReference object Panels : Notifications { From 1a25f7b808b88046f057632cd9e1da077650f325 Mon Sep 17 00:00:00 2001 From: Oscar Chevalier Date: Sat, 27 Dec 2025 05:13:25 +1100 Subject: [PATCH 02/15] fix: make gradlew executable --- library/gradlew | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 library/gradlew diff --git a/library/gradlew b/library/gradlew old mode 100644 new mode 100755 From a3db52c478f54492fc6e35ea7700591cc828c8f9 Mon Sep 17 00:00:00 2001 From: Oscar Chevalier Date: Sat, 27 Dec 2025 05:13:45 +1100 Subject: [PATCH 03/15] fix: include CameraStream module in project --- library/settings.gradle.kts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/library/settings.gradle.kts b/library/settings.gradle.kts index 420e1e8..089968c 100644 --- a/library/settings.gradle.kts +++ b/library/settings.gradle.kts @@ -23,4 +23,5 @@ include(":Panels") include(":Utils") include(":Pinger") include(":Graph") -include(":Lights") \ No newline at end of file +include(":Lights") +include(":CameraStream") \ No newline at end of file From 3a039b60413651f4ed825360a4f9c17f61655b1b Mon Sep 17 00:00:00 2001 From: Oscar Chevalier Date: Fri, 9 Jan 2026 02:51:47 +1100 Subject: [PATCH 04/15] feat: initial attempt at switching to Sloth --- library/Configurables/build.gradle.kts | 26 ++- .../configurables/ConfigurablesPlugin.kt | 190 ++++++------------ .../configurables/GlobalConfigurables.kt | 4 +- .../configurables/PanelsConfigurables.kt | 8 - .../configurables/variables/MyField.kt | 8 +- .../variables/generics/GenericField.kt | 5 +- .../configurables/variables/processValue.kt | 23 ++- library/FullPanels/build.gradle.kts | 4 + library/OpModeControl/build.gradle.kts | 26 ++- .../opmodecontrol/OpModeControlPlugin.kt | 88 ++++---- library/TeamCode/build.gradle | 3 + library/gradle.properties | 5 +- library/settings.gradle.kts | 1 + 13 files changed, 178 insertions(+), 213 deletions(-) delete mode 100644 library/Configurables/src/main/java/com/bylazar/configurables/PanelsConfigurables.kt diff --git a/library/Configurables/build.gradle.kts b/library/Configurables/build.gradle.kts index b47b2f6..4930820 100644 --- a/library/Configurables/build.gradle.kts +++ b/library/Configurables/build.gradle.kts @@ -4,8 +4,9 @@ val pluginVersion = "1.0.5" plugins { id("com.android.library") id("org.jetbrains.kotlin.android") - id("maven-publish") id("com.bylazar.svelte-assets") + id("dev.frozenmilk.publish") version "0.0.5" + id("dev.frozenmilk.doc") version "0.0.5" } svelteAssets { @@ -42,6 +43,14 @@ android { } } +dairyPublishing { + gitDir = file("..") +} + +repositories { + maven("https://repo.dairy.foundation/releases") +} + dependencies { compileOnly("org.firstinspires.ftc:Inspection:11.0.0") compileOnly("org.firstinspires.ftc:Blocks:11.0.0") @@ -52,6 +61,8 @@ dependencies { compileOnly("org.firstinspires.ftc:FtcCommon:11.0.0") compileOnly("org.firstinspires.ftc:Vision:11.0.0") + implementation("dev.frozenmilk.sinister:Sloth:0.2.4") + compileOnly(project(":Panels")) } @@ -61,9 +72,11 @@ afterEvaluate { create("release") { from(components["release"]) - groupId = pluginNamespace.substringBeforeLast('.') + groupId = pluginNamespace.substringBeforeLast('.') + ".sloth" artifactId = pluginNamespace.substringAfterLast('.') - version = pluginVersion + + artifact(dairyDoc.dokkaHtmlJar) + artifact(dairyDoc.dokkaJavadocJar) pom { description.set("Panels Configurables Plugin") @@ -80,12 +93,5 @@ afterEvaluate { } } } - - repositories { - maven { - name = "localDevRepo" - url = uri("file:///C:/Users/lazar/Documents/GitHub/ftcontrol-maven/releases") - } - } } } diff --git a/library/Configurables/src/main/java/com/bylazar/configurables/ConfigurablesPlugin.kt b/library/Configurables/src/main/java/com/bylazar/configurables/ConfigurablesPlugin.kt index 27665e6..8004084 100644 --- a/library/Configurables/src/main/java/com/bylazar/configurables/ConfigurablesPlugin.kt +++ b/library/Configurables/src/main/java/com/bylazar/configurables/ConfigurablesPlugin.kt @@ -1,40 +1,38 @@ package com.bylazar.configurables -import android.R.attr.type import android.content.Context -import com.bylazar.configurables.GlobalConfigurables +import com.bylazar.configurables.GlobalConfigurables.fieldsMap import com.bylazar.configurables.GlobalConfigurables.jvmFields +import com.bylazar.configurables.GlobalConfigurables.loadedFieldsMap import com.bylazar.configurables.annotations.Configurable import com.bylazar.configurables.annotations.IgnoreConfigurable -import com.bylazar.configurables.variables.MyField +import com.bylazar.configurables.annotations.Sorter import com.bylazar.configurables.variables.generics.GenericField import com.bylazar.panels.Panels import com.bylazar.panels.json.SocketMessage import com.bylazar.panels.plugins.BasePluginConfig import com.bylazar.panels.plugins.Plugin -import com.bylazar.panels.reflection.ClassFinder import com.bylazar.panels.server.Socket import com.qualcomm.ftccommon.FtcEventLoop import com.qualcomm.robotcore.eventloop.opmode.OpMode import com.qualcomm.robotcore.eventloop.opmode.OpModeManagerImpl +import dev.frozenmilk.sinister.Scanner +import dev.frozenmilk.sinister.loaders.RootClassLoader +import dev.frozenmilk.sinister.targeting.WideSearch import java.lang.reflect.Modifier -import kotlin.jvm.java -open class ConfigurablesPluginConfig : BasePluginConfig() { -} +open class ConfigurablesPluginConfig : BasePluginConfig() {} -object Plugin : Plugin(ConfigurablesPluginConfig()) { - var fieldsMap = mutableMapOf() - var configurableClasses: List = listOf() - var allFields: MutableList = mutableListOf() +object Plugin : Plugin(ConfigurablesPluginConfig()), Scanner { + private val allFieldsMap: Map> + get() = jvmFields// + .flatMap { it.value.map(GenericField::toJsonType) }// + .groupBy { it.className }.toSortedMap() - val allFieldsMap: Map> - get() = allFields.groupBy { it.className }.toSortedMap() - - var initialAFieldsMap: Map> = mapOf() + private var initialFieldsMap: Map> = mapOf() override fun onNewClient(client: Socket.ClientSocket) { - sendClient(client, "initialConfigurables", initialAFieldsMap) + sendClient(client, "initialConfigurables", initialFieldsMap) sendClient(client, "configurables", allFieldsMap) } @@ -48,110 +46,62 @@ object Plugin : Plugin(ConfigurablesPluginConfig()) { emptyList() } - if(changes == null) return + if (changes == null) return changes.forEach { - val generalRef = GlobalConfigurables.fieldsMap[it.id] ?: return + val generalRef = fieldsMap[it.id] ?: return log("Field id: ${it.id}, New value: ${it.newValueString}") generalRef.setValue(it.newValueString) it.newValueString = generalRef.getValue().toString() - - allFields = jvmFields.map { it.toJsonType }.toMutableList() } send("newConfigurables", changes) } } - fun refreshClass(className: String) { - allFields.removeAll { it.className == className } - - val fields: MutableList = mutableListOf() - - try { - val clazz = Class.forName(className) - log("Inspecting class $className") - fields.addFieldsFromClass(clazz, className) - try { - val companionClazz = Class.forName("${className}\$Companion") - fields.addFieldsFromClass(companionClazz, className) - } catch (e: ClassNotFoundException) { - // no companion found - } - } catch (e: Exception) { - error("Error inspecting class ${className}: ${e.message}") - } - - val newFields = fields.map { it.toJsonType }.toMutableList() - - allFields.addAll(newFields) - - send("configurables", allFieldsMap) - } - override fun onRegister( - panelsInstance: Panels, - context: Context + panelsInstance: Panels, context: Context ) { log("Initializing configurables") - fieldsMap = mutableMapOf() - configurableClasses = listOf() - - GlobalConfigurables.jvmFields = mutableListOf() - GlobalConfigurables.fieldsMap = mutableMapOf() - - configurableClasses = ClassFinder.findClasses( - { clazz -> - val hasConfigurable = - clazz.isAnnotationPresent(Configurable::class.java) - val hasIgnoreConfigurable = - clazz.isAnnotationPresent(IgnoreConfigurable::class.java) - val shouldKeep = - hasConfigurable && !hasIgnoreConfigurable - - shouldKeep - } - ) + } - log("Configurable classes: ${configurableClasses.map { it.className }}") + override fun onAttachEventLoop(eventLoop: FtcEventLoop) { + } - val fields: MutableList = mutableListOf() + override fun onOpModeManager(o: OpModeManagerImpl) { + } - configurableClasses.forEach { - val className = it.className + override fun onOpModePreInit(opMode: OpMode) { + } - try { - val clazz = Class.forName(className) - log("Inspecting class $className") - fields.addFieldsFromClass(clazz, className) - try { - val companionClazz = Class.forName("${className}\$Companion") - fields.addFieldsFromClass(companionClazz, className) - } catch (e: ClassNotFoundException) { - // no companion found - } - } catch (e: Exception) { - error("Error inspecting class ${className}: ${e.message}") - } - } + override fun onOpModePreStart(opMode: OpMode) { + } - log("Configurable fields: ${fields.map { it.name }}") + override fun onOpModePostStop(opMode: OpMode) { + } - allFields = jvmFields.map { it.toJsonType }.filter { it.className != "class java.lang.ClassLoader" && it.className != "class sun.misc.Unsafe" }.toMutableList() - initialAFieldsMap = allFieldsMap + override fun onEnablePanels() { + } - send("initialConfigurables", initialAFieldsMap) - send("configurables", allFieldsMap) + override fun onDisablePanels() { } - private fun MutableList.addFieldsFromClass( - clazz: Class<*>, - originalClassName: String - ) { - log("Sorting fields for class ${clazz.simpleName}") - val fields = clazz.declaredFields.sortedBy { field -> - val sort = field.getAnnotation(com.bylazar.configurables.annotations.Sorter::class.java)?.sort + // + // Scanner + // + + override val loadAdjacencyRule = Scanner.INDEPENDENT + override val unloadAdjacencyRule = Scanner.INDEPENDENT + override val targets = WideSearch() + + override fun scan(loader: ClassLoader, cls: Class<*>) { + if (!cls.isAnnotationPresent(Configurable::class.java)) return + if (cls.isAnnotationPresent(IgnoreConfigurable::class.java)) return + + log("Sorting fields for class ${cls.simpleName}") + val fields = cls.declaredFields.sortedBy { field -> + val sort = field.getAnnotation(Sorter::class.java)?.sort log("Field ${field.name} has sort value: $sort") sort ?: Int.MAX_VALUE } @@ -162,16 +112,6 @@ object Plugin : Plugin(ConfigurablesPluginConfig()) { val isStatic = Modifier.isStatic(field.modifiers) val isIgnored = field.isAnnotationPresent(IgnoreConfigurable::class.java) - val isPrivate = Modifier.isPrivate(field.modifiers) - val isNull = try { - if (field.get(null) == null) { - println("PANELS: Field ${field.name} in $clazz is null") - true - } else false - } catch (t: Throwable) { - false - } - val isJvmField = !isFinal && isStatic && !isIgnored val fieldTypeName = field.type.canonicalName ?: "" @@ -179,38 +119,30 @@ object Plugin : Plugin(ConfigurablesPluginConfig()) { log("Found field of $fieldTypeName / $isJvmField") if (isJvmField) { - val displayClassName = - if (clazz.name.endsWith("\$Companion")) originalClassName else clazz.name - val genericField = GenericField(className = displayClassName, reference = field) + val displayClassName = cls.kotlin.simpleName ?: cls.name + val genericField = GenericField(classLoader = loader, className = displayClassName, reference = field) log("Adding field $genericField / ${genericField.type} / ${genericField.value} / ${genericField.isNull}") - add(genericField) - GlobalConfigurables.jvmFields.add(genericField) + jvmFields.getOrPut(loader, ::mutableListOf).add(genericField) } } catch (t: Throwable) { - error("Error inspecting field ${field.name} in $clazz: ${t.message}") + error("Error inspecting field ${field.name} in $cls: ${t.message}") } } } - override fun onAttachEventLoop(eventLoop: FtcEventLoop) { - } - - override fun onOpModeManager(o: OpModeManagerImpl) { - } - - override fun onOpModePreInit(opMode: OpMode) { - } - - override fun onOpModePreStart(opMode: OpMode) { - } - - override fun onOpModePostStop(opMode: OpMode) { + override fun afterScan(loader: ClassLoader) { + if (loader is RootClassLoader) { + initialFieldsMap = allFieldsMap + send("initialConfigurables", initialFieldsMap) + } + send("configurables", allFieldsMap) } - - override fun onEnablePanels() { + override fun beforeUnload(loader: ClassLoader) { + jvmFields.remove(loader) + loadedFieldsMap.remove(loader)?.forEach(fieldsMap::remove) + send("configurables", allFieldsMap) } - override fun onDisablePanels() { - } + override fun unload(loader: ClassLoader, cls: Class<*>) {} } \ No newline at end of file diff --git a/library/Configurables/src/main/java/com/bylazar/configurables/GlobalConfigurables.kt b/library/Configurables/src/main/java/com/bylazar/configurables/GlobalConfigurables.kt index 8f71396..2d97f5e 100644 --- a/library/Configurables/src/main/java/com/bylazar/configurables/GlobalConfigurables.kt +++ b/library/Configurables/src/main/java/com/bylazar/configurables/GlobalConfigurables.kt @@ -4,7 +4,7 @@ import com.bylazar.configurables.variables.MyField import com.bylazar.configurables.variables.generics.GenericField object GlobalConfigurables { - var jvmFields = mutableListOf() - + var jvmFields = mutableMapOf>() + var loadedFieldsMap = mutableMapOf>() var fieldsMap = mutableMapOf() } \ No newline at end of file diff --git a/library/Configurables/src/main/java/com/bylazar/configurables/PanelsConfigurables.kt b/library/Configurables/src/main/java/com/bylazar/configurables/PanelsConfigurables.kt deleted file mode 100644 index c5f8ae8..0000000 --- a/library/Configurables/src/main/java/com/bylazar/configurables/PanelsConfigurables.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.bylazar.configurables - -object PanelsConfigurables { - fun refreshClass(cls: Any) { - val name = cls::class.qualifiedName ?: return - Plugin.refreshClass(name) - } -} \ No newline at end of file diff --git a/library/Configurables/src/main/java/com/bylazar/configurables/variables/MyField.kt b/library/Configurables/src/main/java/com/bylazar/configurables/variables/MyField.kt index ebfe43d..d136253 100644 --- a/library/Configurables/src/main/java/com/bylazar/configurables/variables/MyField.kt +++ b/library/Configurables/src/main/java/com/bylazar/configurables/variables/MyField.kt @@ -10,9 +10,10 @@ import java.util.Locale import java.util.UUID import kotlin.text.* -class MyField( +class MyField constructor( val name: String, val type: Class<*>, + val classLoader: ClassLoader, var isAccessible: Boolean, var get: (instance: Any?) -> Any?, var set: (instance: Any?, newValue: Any?) -> Unit, @@ -32,6 +33,7 @@ class MyField( init { if (ref != null) ref.isAccessible = true GlobalConfigurables.fieldsMap[id] = this + GlobalConfigurables.loadedFieldsMap.getOrPut(classLoader, ::mutableListOf).add(id) } fun getValue(recursionDepth: Int = 0): Any? { @@ -79,10 +81,11 @@ class MyField( } } -fun convertToMyField(field: Field): MyField { +fun convertToMyField(classLoader: ClassLoader, field: Field): MyField { return MyField( name = field.name, type = field.type, + classLoader = classLoader, isAccessible = field.isAccessible, get = field::get, set = field::set, @@ -95,6 +98,7 @@ fun convertToMyField(field: MyField, parentField: MyField?, className: String): return MyField( name = field.name, type = field.type, + classLoader = field.classLoader, isAccessible = field.isAccessible, get = field.get, set = field.set, diff --git a/library/Configurables/src/main/java/com/bylazar/configurables/variables/generics/GenericField.kt b/library/Configurables/src/main/java/com/bylazar/configurables/variables/generics/GenericField.kt index d5dabf4..05d136b 100644 --- a/library/Configurables/src/main/java/com/bylazar/configurables/variables/generics/GenericField.kt +++ b/library/Configurables/src/main/java/com/bylazar/configurables/variables/generics/GenericField.kt @@ -10,12 +10,13 @@ import com.bylazar.configurables.variables.processValue import java.lang.reflect.Field class GenericField( + classLoader: ClassLoader, var className: String, var reference: Field, ) { - val type = getType(reference.type, convertToMyField(reference), null) - val value: GenericVariable = processValue(0, className, type, convertToMyField(reference), null) + val type = getType(reference.type, convertToMyField(classLoader, reference), null) + val value: GenericVariable = processValue(0, className, classLoader, type, convertToMyField(classLoader, reference), null) val isNull: Boolean get() = toJsonType.type == BaseTypes.JSON_ERROR diff --git a/library/Configurables/src/main/java/com/bylazar/configurables/variables/processValue.kt b/library/Configurables/src/main/java/com/bylazar/configurables/variables/processValue.kt index f30c337..12fd31e 100644 --- a/library/Configurables/src/main/java/com/bylazar/configurables/variables/processValue.kt +++ b/library/Configurables/src/main/java/com/bylazar/configurables/variables/processValue.kt @@ -23,6 +23,7 @@ class RecursionDetectedException(val fieldName: String) : RuntimeException() fun processValue( recursionDepth: Int, className: String, + classLoader: ClassLoader, type: BaseTypes, reference: MyField, parentReference: MyField?, @@ -58,7 +59,8 @@ fun processValue( if (type == BaseTypes.CUSTOM) { reference.isAccessible = true val customValues = reference.type.declaredFields.sortedBy { field -> - field.getAnnotation(com.bylazar.configurables.annotations.Sorter::class.java)?.sort ?: Int.MAX_VALUE + field.getAnnotation(com.bylazar.configurables.annotations.Sorter::class.java)?.sort + ?: Int.MAX_VALUE }.mapNotNull { field -> try { field.isAccessible = true @@ -75,13 +77,15 @@ fun processValue( // return@mapNotNull null // } if (field.type == reference.type) { - val newField = GenericField(reference.type.toString(), field) - val alreadyExists = GlobalConfigurables.jvmFields.any { - it.className == newField.className && it.reference.name == newField.reference.name + val newField = GenericField(classLoader, reference.type.toString(), field) + val alreadyExists = GlobalConfigurables.jvmFields.any { (_, fields) -> + fields.any { + it.className == newField.className && it.reference.name == newField.reference.name + } } if (!alreadyExists) { - GlobalConfigurables.jvmFields.add(newField) + GlobalConfigurables.jvmFields.getOrPut(classLoader, ::mutableListOf).add(newField) ConfigurablesLogger.log("Moved field to global configurable: ${reference.name} ${reference.type} ${field.name}") } else { ConfigurablesLogger.log("Skipped duplicate configurable: ${reference.name} ${reference.type} ${field.name}") @@ -90,11 +94,12 @@ fun processValue( return@mapNotNull null } - val customNewField = convertToMyField(field) + val customNewField = convertToMyField(classLoader, field) processValue( recursionDepth + 1, className, + classLoader, getType(field.type, customNewField, reference), customNewField, currentManager, @@ -134,6 +139,7 @@ fun processValue( val currentElementReference = MyField( name = i.toString(), type = cType, + classLoader = classLoader, isAccessible = true, get = { instance -> Array.get(instance, i) @@ -147,6 +153,7 @@ fun processValue( processValue( recursionDepth + 1, className, + classLoader, itemType, currentElementReference, currentManager, @@ -186,6 +193,7 @@ fun processValue( val currentElementReference = MyField( name = index.toString(), type = cType, + classLoader = classLoader, isAccessible = true, get = { instance -> listInstance.getOrNull(index) @@ -203,6 +211,7 @@ fun processValue( processValue( recursionDepth + 1, className, + classLoader, itemType, currentElementReference, currentManager, @@ -228,6 +237,7 @@ fun processValue( val currentElementReference = MyField( name = key.toString(), type = mapInstance[key]?.javaClass ?: Any::class.java, + classLoader = classLoader, isAccessible = true, get = { instance -> mapInstance[key] @@ -241,6 +251,7 @@ fun processValue( processValue( recursionDepth + 1, className, + classLoader, itemType, currentElementReference, currentManager, diff --git a/library/FullPanels/build.gradle.kts b/library/FullPanels/build.gradle.kts index 5a66b0a..6ea6e5c 100644 --- a/library/FullPanels/build.gradle.kts +++ b/library/FullPanels/build.gradle.kts @@ -42,6 +42,10 @@ android { } } +repositories { + maven("https://repo.dairy.foundation/releases") +} + dependencies { compileOnly("org.firstinspires.ftc:Inspection:11.0.0") compileOnly("org.firstinspires.ftc:Blocks:11.0.0") diff --git a/library/OpModeControl/build.gradle.kts b/library/OpModeControl/build.gradle.kts index 9987f5f..7fa4760 100644 --- a/library/OpModeControl/build.gradle.kts +++ b/library/OpModeControl/build.gradle.kts @@ -4,8 +4,9 @@ val pluginVersion = "1.0.3" plugins { id("com.android.library") id("org.jetbrains.kotlin.android") - id("maven-publish") id("com.bylazar.svelte-assets") + id("dev.frozenmilk.publish") version "0.0.5" + id("dev.frozenmilk.doc") version "0.0.5" } svelteAssets { @@ -42,6 +43,14 @@ android { } } +dairyPublishing { + gitDir = file("..") +} + +repositories { + maven("https://repo.dairy.foundation/releases") +} + dependencies { compileOnly("org.firstinspires.ftc:Inspection:11.0.0") compileOnly("org.firstinspires.ftc:Blocks:11.0.0") @@ -52,6 +61,8 @@ dependencies { compileOnly("org.firstinspires.ftc:FtcCommon:11.0.0") compileOnly("org.firstinspires.ftc:Vision:11.0.0") + implementation("dev.frozenmilk.sinister:Sloth:0.2.4") + compileOnly(project(":Panels")) } @@ -61,9 +72,11 @@ afterEvaluate { create("release") { from(components["release"]) - groupId = pluginNamespace.substringBeforeLast('.') + groupId = pluginNamespace.substringBeforeLast('.') + ".sloth" artifactId = pluginNamespace.substringAfterLast('.') - version = pluginVersion + + artifact(dairyDoc.dokkaHtmlJar) + artifact(dairyDoc.dokkaJavadocJar) pom { description.set("Panels OpModeControl Plugin") @@ -80,12 +93,5 @@ afterEvaluate { } } } - - repositories { - maven { - name = "localDevRepo" - url = uri("file:///C:/Users/lazar/Documents/GitHub/ftcontrol-maven/releases") - } - } } } diff --git a/library/OpModeControl/src/main/java/com/bylazar/opmodecontrol/OpModeControlPlugin.kt b/library/OpModeControl/src/main/java/com/bylazar/opmodecontrol/OpModeControlPlugin.kt index 98b7fbf..dea76d6 100644 --- a/library/OpModeControl/src/main/java/com/bylazar/opmodecontrol/OpModeControlPlugin.kt +++ b/library/OpModeControl/src/main/java/com/bylazar/opmodecontrol/OpModeControlPlugin.kt @@ -1,7 +1,6 @@ package com.bylazar.opmodecontrol import android.content.Context -import android.os.SystemClock import com.bylazar.panels.Panels import com.bylazar.panels.plugins.BasePluginConfig import com.bylazar.panels.plugins.Plugin @@ -9,22 +8,35 @@ import com.bylazar.panels.server.Socket import com.qualcomm.ftccommon.FtcEventLoop import com.qualcomm.robotcore.eventloop.opmode.OpMode import com.qualcomm.robotcore.eventloop.opmode.OpModeManagerImpl +import dev.frozenmilk.sinister.Scanner +import dev.frozenmilk.sinister.sdk.apphooks.SDKOpModeRegistrar +import dev.frozenmilk.sinister.sdk.opmodes.OpModeScanner +import dev.frozenmilk.sinister.sdk.opmodes.SinisterRegisteredOpModes +import dev.frozenmilk.sinister.targeting.NarrowSearch +import dev.frozenmilk.util.graph.rule.dependsOn +import dev.frozenmilk.util.graph.rule.dependsOnClass import org.firstinspires.ftc.robotcore.internal.opmode.OpModeMeta -import org.firstinspires.ftc.robotcore.internal.opmode.RegisteredOpModes import java.lang.ref.WeakReference import java.util.concurrent.Executors import java.util.concurrent.ScheduledFuture import java.util.concurrent.TimeUnit -open class OpModeControlPluginConfig : BasePluginConfig() { -} +open class OpModeControlPluginConfig : BasePluginConfig() {} + +object Plugin : Plugin(OpModeControlPluginConfig()), Scanner { + val opModeList: List + get() = SinisterRegisteredOpModes.opModes.mapNotNull { opModeMeta -> + if (opModeMeta.flavor == OpModeMeta.Flavor.SYSTEM) null + else OpModeDetails( + name = opModeMeta.name, + group = opModeMeta.group, + flavour = opModeMeta.flavor, + source = opModeMeta.source ?: OpModeMeta.Source.ANDROID_STUDIO, + defaultGroup = OpModeMeta.DefaultGroup, + autoTransition = opModeMeta.autoTransition ?: "" + ) + }.sortedWith(compareBy({ it.group }, { it.name })) -object Plugin : Plugin(OpModeControlPluginConfig()) { - var opModeList: MutableList = mutableListOf() - set(value) { - field = value - send("opModesList", OpModesList(value)) - } var status = OpModeStatus.STOPPED var activeOpMode: OpMode? = null var activeOpModeStartTimestamp: Long? = null @@ -58,6 +70,7 @@ object Plugin : Plugin(OpModeControlPluginConfig()) { private val scheduler = Executors.newSingleThreadScheduledExecutor { r -> Thread(r, "OpModeControl-Ticker") } + @Volatile private var tickTask: ScheduledFuture<*>? = null @@ -107,8 +120,7 @@ object Plugin : Plugin(OpModeControlPluginConfig()) { } override fun onRegister( - panelsInstance: Panels, - context: Context + panelsInstance: Panels, context: Context ) { } @@ -122,9 +134,6 @@ object Plugin : Plugin(OpModeControlPluginConfig()) { opModeManagerRef = WeakReference(o) activeOpMode = null activeOpModeName = "" - opModeList.clear() - val t = Thread(FetcherRoutine()) - t.start() activeOpModeStartTimestamp = null } @@ -170,39 +179,32 @@ object Plugin : Plugin(OpModeControlPluginConfig()) { sendActiveOpMode() } - class FetcherRoutine : Runnable { - override fun run() { - RegisteredOpModes.getInstance().waitOpModesRegistered() - - var list = ArrayList() - for (opModeMeta in RegisteredOpModes.getInstance().opModes) { - if (opModeMeta.flavor != OpModeMeta.Flavor.SYSTEM) { - list.add( - OpModeDetails( - name = opModeMeta.name, - group = opModeMeta.group, - flavour = opModeMeta.flavor, - source = opModeMeta.source ?: OpModeMeta.Source.ANDROID_STUDIO, - defaultGroup = OpModeMeta.DefaultGroup, - autoTransition = opModeMeta.autoTransition ?: "" - ) - ) - } - } - - opModeList = list.sortedWith(compareBy({ it.group }, { it.name })).toMutableList() - log("OpModes: ${opModeList.joinToString(", ", transform = { it.name })}") - - send("opModesList", OpModesList(opModeList)) - sendActiveOpMode() - } + override fun onEnablePanels() { } + override fun onDisablePanels() { + } - override fun onEnablePanels() { + override val loadAdjacencyRule = + dependsOnClass(OpModeScanner::class.java).and(dependsOn(SDKOpModeRegistrar)) + override val unloadAdjacencyRule = + dependsOnClass(OpModeScanner::class.java).and(dependsOn(SDKOpModeRegistrar)) + override val targets = NarrowSearch() + + override fun scan(loader: ClassLoader, cls: Class<*>) {} + override fun afterScan(loader: ClassLoader) { + val opModeList = opModeList + log("OpModes: ${opModeList.joinToString(", ", transform = { it.name })}") + send("opModesList", OpModesList(opModeList)) + sendActiveOpMode() } - override fun onDisablePanels() { + override fun unload(loader: ClassLoader, cls: Class<*>) {} + override fun afterUnload(loader: ClassLoader) { + val opModeList = opModeList + log("OpModes: ${opModeList.joinToString(", ", transform = { it.name })}") + send("opModesList", OpModesList(opModeList)) + sendActiveOpMode() } } \ No newline at end of file diff --git a/library/TeamCode/build.gradle b/library/TeamCode/build.gradle index 3e52c54..9ac8b4c 100644 --- a/library/TeamCode/build.gradle +++ b/library/TeamCode/build.gradle @@ -35,6 +35,9 @@ android { } repositories { + maven { + url "https://repo.dairy.foundation/releases" + } } dependencies { diff --git a/library/gradle.properties b/library/gradle.properties index f5935e9..8867ee4 100644 --- a/library/gradle.properties +++ b/library/gradle.properties @@ -9,4 +9,7 @@ android.enableJetifier=false # Allow Gradle to use up to 1 GB of RAM org.gradle.jvmargs=-Xmx1024M -android.nonTransitiveRClass=false \ No newline at end of file +android.nonTransitiveRClass=false + +org.jetbrains.dokka.experimental.gradle.pluginMode=V2Enabled +org.jetbrains.dokka.experimental.gradle.pluginMode.noWarn=true \ No newline at end of file diff --git a/library/settings.gradle.kts b/library/settings.gradle.kts index 089968c..46e31c5 100644 --- a/library/settings.gradle.kts +++ b/library/settings.gradle.kts @@ -2,6 +2,7 @@ pluginManagement { includeBuild("plugin-svelte-assets") repositories { gradlePluginPortal() + maven("https://repo.dairy.foundation/releases") } } From 716fed024268f317b9e7c4f1caa6a13b5695f5f6 Mon Sep 17 00:00:00 2001 From: Oscar Chevalier Date: Sun, 11 Jan 2026 05:51:17 +1100 Subject: [PATCH 05/15] chore: increase gradle memory limits --- library/gradle.properties | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/gradle.properties b/library/gradle.properties index 8867ee4..e392bd1 100644 --- a/library/gradle.properties +++ b/library/gradle.properties @@ -6,8 +6,8 @@ android.useAndroidX=true # We no longer need to auto-convert third-party libraries to use AndroidX, which slowed down the build android.enableJetifier=false -# Allow Gradle to use up to 1 GB of RAM -org.gradle.jvmargs=-Xmx1024M +# Allow Gradle to use up to 2 GB of RAM +org.gradle.jvmargs=-Xmx2048M android.nonTransitiveRClass=false From 0ffb90b92c2eac5c8cf42ca39804611e91bfa776 Mon Sep 17 00:00:00 2001 From: Oscar Chevalier Date: Sun, 11 Jan 2026 06:08:24 +1100 Subject: [PATCH 06/15] rework: overhaul frontend asset building * completely rewrite the gradle logic for the plugin * link frontend asset builds to the core build logic to ensure they stay in sync * download a local version of bun for the project * cache the frontend build to ensure that build times stay fast * moved the output directory to be in a more appropriate generated sources location, rather than make it look like actual source code --- examples/.gitignore | 1 + library/.gitignore | 1 + library/Battery/build.gradle.kts | 4 +- library/CameraStream/build.gradle.kts | 6 +- library/Capture/build.gradle.kts | 4 +- library/Configurables/build.gradle.kts | 4 +- library/Docs/build.gradle.kts | 4 +- library/ExamplePlugin/build.gradle.kts | 4 +- library/Field/build.gradle.kts | 4 +- library/FullPanels/build.gradle.kts | 4 +- library/Gamepad/build.gradle.kts | 4 +- library/Graph/build.gradle.kts | 4 +- library/Lights/build.gradle.kts | 4 +- library/LimelightProxy/build.gradle.kts | 6 +- library/OpModeControl/build.gradle.kts | 4 +- library/Panels/build.gradle.kts | 9 +- library/Pinger/build.gradle.kts | 4 +- library/Telemetry/build.gradle.kts | 4 +- library/Themes/build.gradle.kts | 4 +- library/Utils/build.gradle.kts | 4 +- library/plugin-svelte-assets/build.gradle.kts | 5 + .../kotlin/com/bylazar/SvelteAssetsPlugin.kt | 294 +++++------------- .../bylazar/SvelteAssetsPluginExtension.kt | 8 + .../main/kotlin/com/bylazar/tasks/BunTask.kt | 46 +++ .../com/bylazar/tasks/InstallBunLocally.kt | 21 ++ 25 files changed, 179 insertions(+), 278 deletions(-) create mode 100644 library/plugin-svelte-assets/src/main/kotlin/com/bylazar/SvelteAssetsPluginExtension.kt create mode 100644 library/plugin-svelte-assets/src/main/kotlin/com/bylazar/tasks/BunTask.kt create mode 100644 library/plugin-svelte-assets/src/main/kotlin/com/bylazar/tasks/InstallBunLocally.kt diff --git a/examples/.gitignore b/examples/.gitignore index 8ea3a65..e9b0d3c 100644 --- a/examples/.gitignore +++ b/examples/.gitignore @@ -1,5 +1,6 @@ # dependencies (rm -rf bun.lock && bun install --no-cachenstall) node_modules +.bun # output out diff --git a/library/.gitignore b/library/.gitignore index 8ea3a65..e9b0d3c 100644 --- a/library/.gitignore +++ b/library/.gitignore @@ -1,5 +1,6 @@ # dependencies (rm -rf bun.lock && bun install --no-cachenstall) node_modules +.bun # output out diff --git a/library/Battery/build.gradle.kts b/library/Battery/build.gradle.kts index f5bbe50..81b0765 100644 --- a/library/Battery/build.gradle.kts +++ b/library/Battery/build.gradle.kts @@ -9,9 +9,7 @@ plugins { } svelteAssets { - webAppPath = "web" - buildDirPath = "dist" - assetsPath = "web/plugins/$pluginNamespace" + assetsPath = assetPathForPlugin(pluginNamespace) } android { diff --git a/library/CameraStream/build.gradle.kts b/library/CameraStream/build.gradle.kts index e1f2e0f..efa462b 100644 --- a/library/CameraStream/build.gradle.kts +++ b/library/CameraStream/build.gradle.kts @@ -1,5 +1,3 @@ -import org.gradle.kotlin.dsl.implementation - val pluginNamespace = "com.bylazar.camerastream" val pluginVersion = "1.0.0" @@ -11,9 +9,7 @@ plugins { } svelteAssets { - webAppPath = "web" - buildDirPath = "dist" - assetsPath = "web/plugins/$pluginNamespace" + assetsPath = assetPathForPlugin(pluginNamespace) } android { diff --git a/library/Capture/build.gradle.kts b/library/Capture/build.gradle.kts index 221d9a2..490c80c 100644 --- a/library/Capture/build.gradle.kts +++ b/library/Capture/build.gradle.kts @@ -9,9 +9,7 @@ plugins { } svelteAssets { - webAppPath = "web" - buildDirPath = "dist" - assetsPath = "web/plugins/$pluginNamespace" + assetsPath = assetPathForPlugin(pluginNamespace) } android { diff --git a/library/Configurables/build.gradle.kts b/library/Configurables/build.gradle.kts index 4930820..678b626 100644 --- a/library/Configurables/build.gradle.kts +++ b/library/Configurables/build.gradle.kts @@ -10,9 +10,7 @@ plugins { } svelteAssets { - webAppPath = "web" - buildDirPath = "dist" - assetsPath = "web/plugins/$pluginNamespace" + assetsPath = assetPathForPlugin(pluginNamespace) } android { diff --git a/library/Docs/build.gradle.kts b/library/Docs/build.gradle.kts index 08064e3..aea7749 100644 --- a/library/Docs/build.gradle.kts +++ b/library/Docs/build.gradle.kts @@ -9,9 +9,7 @@ plugins { } svelteAssets { - webAppPath = "web" - buildDirPath = "dist" - assetsPath = "web/plugins/$pluginNamespace" + assetsPath = assetPathForPlugin(pluginNamespace) } android { diff --git a/library/ExamplePlugin/build.gradle.kts b/library/ExamplePlugin/build.gradle.kts index ccc2032..82f496f 100644 --- a/library/ExamplePlugin/build.gradle.kts +++ b/library/ExamplePlugin/build.gradle.kts @@ -9,9 +9,7 @@ plugins { } svelteAssets { - webAppPath = "web" - buildDirPath = "dist" - assetsPath = "web/plugins/$pluginNamespace" + assetsPath = assetPathForPlugin(pluginNamespace) } android { diff --git a/library/Field/build.gradle.kts b/library/Field/build.gradle.kts index dca32ac..8dbebe8 100644 --- a/library/Field/build.gradle.kts +++ b/library/Field/build.gradle.kts @@ -9,9 +9,7 @@ plugins { } svelteAssets { - webAppPath = "web" - buildDirPath = "dist" - assetsPath = "web/plugins/$pluginNamespace" + assetsPath = assetPathForPlugin(pluginNamespace) } android { diff --git a/library/FullPanels/build.gradle.kts b/library/FullPanels/build.gradle.kts index 6ea6e5c..cebdef2 100644 --- a/library/FullPanels/build.gradle.kts +++ b/library/FullPanels/build.gradle.kts @@ -9,9 +9,7 @@ plugins { } svelteAssets { - webAppPath = "web" - buildDirPath = "dist" - assetsPath = "web/plugins/$pluginNamespace" + assetsPath = assetPathForPlugin(pluginNamespace) } android { diff --git a/library/Gamepad/build.gradle.kts b/library/Gamepad/build.gradle.kts index bfafbd3..f32fbdf 100644 --- a/library/Gamepad/build.gradle.kts +++ b/library/Gamepad/build.gradle.kts @@ -9,9 +9,7 @@ plugins { } svelteAssets { - webAppPath = "web" - buildDirPath = "dist" - assetsPath = "web/plugins/$pluginNamespace" + assetsPath = assetPathForPlugin(pluginNamespace) } android { diff --git a/library/Graph/build.gradle.kts b/library/Graph/build.gradle.kts index 4cad699..8b5bc7c 100644 --- a/library/Graph/build.gradle.kts +++ b/library/Graph/build.gradle.kts @@ -9,9 +9,7 @@ plugins { } svelteAssets { - webAppPath = "web" - buildDirPath = "dist" - assetsPath = "web/plugins/$pluginNamespace" + assetsPath = assetPathForPlugin(pluginNamespace) } android { diff --git a/library/Lights/build.gradle.kts b/library/Lights/build.gradle.kts index 46942d5..7416d31 100644 --- a/library/Lights/build.gradle.kts +++ b/library/Lights/build.gradle.kts @@ -9,9 +9,7 @@ plugins { } svelteAssets { - webAppPath = "web" - buildDirPath = "dist" - assetsPath = "web/plugins/$pluginNamespace" + assetsPath = assetPathForPlugin(pluginNamespace) } android { diff --git a/library/LimelightProxy/build.gradle.kts b/library/LimelightProxy/build.gradle.kts index 6f51a5c..fbeb14e 100644 --- a/library/LimelightProxy/build.gradle.kts +++ b/library/LimelightProxy/build.gradle.kts @@ -1,5 +1,3 @@ -import org.gradle.kotlin.dsl.implementation - val pluginNamespace = "com.bylazar.limelightproxy" val pluginVersion = "1.0.4" @@ -11,9 +9,7 @@ plugins { } svelteAssets { - webAppPath = "web" - buildDirPath = "dist" - assetsPath = "web/plugins/$pluginNamespace" + assetsPath = assetPathForPlugin(pluginNamespace) } android { diff --git a/library/OpModeControl/build.gradle.kts b/library/OpModeControl/build.gradle.kts index 7fa4760..922d8e2 100644 --- a/library/OpModeControl/build.gradle.kts +++ b/library/OpModeControl/build.gradle.kts @@ -10,9 +10,7 @@ plugins { } svelteAssets { - webAppPath = "web" - buildDirPath = "dist" - assetsPath = "web/plugins/$pluginNamespace" + assetsPath = assetPathForPlugin(pluginNamespace) } android { diff --git a/library/Panels/build.gradle.kts b/library/Panels/build.gradle.kts index eea1a04..5d851a3 100644 --- a/library/Panels/build.gradle.kts +++ b/library/Panels/build.gradle.kts @@ -1,3 +1,6 @@ +val pluginNamespace = "com.bylazar.panels" +val pluginVersion = "1.0.5" + plugins { id("com.android.library") id("org.jetbrains.kotlin.android") @@ -5,13 +8,9 @@ plugins { id("com.bylazar.svelte-assets") } -val pluginNamespace = "com.bylazar.panels" -val pluginVersion = "1.0.5" - svelteAssets { - webAppPath = "web" - buildDirPath = "build" assetsPath = "web" + buildDirPath = "build" } android { diff --git a/library/Pinger/build.gradle.kts b/library/Pinger/build.gradle.kts index 1b07423..871cf07 100644 --- a/library/Pinger/build.gradle.kts +++ b/library/Pinger/build.gradle.kts @@ -9,9 +9,7 @@ plugins { } svelteAssets { - webAppPath = "web" - buildDirPath = "dist" - assetsPath = "web/plugins/$pluginNamespace" + assetsPath = assetPathForPlugin(pluginNamespace) } android { diff --git a/library/Telemetry/build.gradle.kts b/library/Telemetry/build.gradle.kts index 5470ad7..7ef3dcc 100644 --- a/library/Telemetry/build.gradle.kts +++ b/library/Telemetry/build.gradle.kts @@ -9,9 +9,7 @@ plugins { } svelteAssets { - webAppPath = "web" - buildDirPath = "dist" - assetsPath = "web/plugins/$pluginNamespace" + assetsPath = assetPathForPlugin(pluginNamespace) } android { diff --git a/library/Themes/build.gradle.kts b/library/Themes/build.gradle.kts index 686e4f2..961a9e7 100644 --- a/library/Themes/build.gradle.kts +++ b/library/Themes/build.gradle.kts @@ -9,9 +9,7 @@ plugins { } svelteAssets { - webAppPath = "web" - buildDirPath = "dist" - assetsPath = "web/plugins/$pluginNamespace" + assetsPath = assetPathForPlugin(pluginNamespace) } android { diff --git a/library/Utils/build.gradle.kts b/library/Utils/build.gradle.kts index b576893..12d836c 100644 --- a/library/Utils/build.gradle.kts +++ b/library/Utils/build.gradle.kts @@ -9,9 +9,7 @@ plugins { } svelteAssets { - webAppPath = "web" - buildDirPath = "dist" - assetsPath = "web/plugins/$pluginNamespace" + assetsPath = assetPathForPlugin(pluginNamespace) } android { diff --git a/library/plugin-svelte-assets/build.gradle.kts b/library/plugin-svelte-assets/build.gradle.kts index 823e1a6..f375a4f 100644 --- a/library/plugin-svelte-assets/build.gradle.kts +++ b/library/plugin-svelte-assets/build.gradle.kts @@ -12,6 +12,11 @@ repositories { mavenCentral() } +dependencies { + //noinspection AndroidGradlePluginVersion + compileOnly("com.android.tools.build:gradle:8.7.0") +} + gradlePlugin { plugins { create("svelteAssetsPlugin") { diff --git a/library/plugin-svelte-assets/src/main/kotlin/com/bylazar/SvelteAssetsPlugin.kt b/library/plugin-svelte-assets/src/main/kotlin/com/bylazar/SvelteAssetsPlugin.kt index 061417f..e81f1db 100644 --- a/library/plugin-svelte-assets/src/main/kotlin/com/bylazar/SvelteAssetsPlugin.kt +++ b/library/plugin-svelte-assets/src/main/kotlin/com/bylazar/SvelteAssetsPlugin.kt @@ -1,251 +1,109 @@ package com.bylazar +import com.android.build.api.variant.LibraryAndroidComponentsExtension +import com.android.build.gradle.internal.tasks.factory.dependsOn +import com.bylazar.tasks.BunTask +import com.bylazar.tasks.InstallBunLocally import org.gradle.api.Plugin import org.gradle.api.Project -import org.gradle.api.Task -import org.gradle.api.GradleException -import org.gradle.api.logging.LogLevel import org.gradle.api.tasks.Copy import org.gradle.api.tasks.Delete -import org.gradle.api.tasks.Exec -import org.gradle.api.services.BuildService -import org.gradle.api.services.BuildServiceParameters +import org.gradle.kotlin.dsl.register import java.io.File -import java.io.FileOutputStream -import java.io.OutputStream -import java.time.LocalDateTime -import java.time.format.DateTimeFormatter - -open class SvelteAssetsPluginExtension { - var webAppPath: String = "web" - var buildDirPath: String = "build" - var assetsPath: String = "web" - var useNpm: Boolean = false - var maxParallelSvelteBuilds: Int = 2 -} - -abstract class SvelteSingleSlotService : - BuildService, AutoCloseable { - override fun close() {} -} - -class TeeOutputStream( - private val a: OutputStream, - private val b: OutputStream -) : OutputStream() { - override fun write(bte: Int) { - a.write(bte); b.write(bte) - } - override fun write(btes: ByteArray) { - a.write(btes); b.write(btes) - } - override fun write(btes: ByteArray, off: Int, len: Int) { - a.write(btes, off, len); b.write(btes, off, len) - } - override fun flush() { a.flush(); b.flush() } - override fun close() { a.close(); b.close() } -} - -private object StdOutStream : OutputStream() { - override fun write(b: Int) = System.out.write(b) - override fun write(b: ByteArray, off: Int, len: Int) = System.out.write(b, off, len) -} - -private object StdErrStream : OutputStream() { - override fun write(b: Int) = System.err.write(b) - override fun write(b: ByteArray, off: Int, len: Int) = System.err.write(b, off, len) -} +@Suppress("LocalVariableName", "unused") class SvelteAssetsPlugin : Plugin { override fun apply(project: Project) { - val extension = project.extensions.create("svelteAssets", SvelteAssetsPluginExtension::class.java) + val extension = + project.extensions.create("svelteAssets", SvelteAssetsPluginExtension::class.java) - project.plugins.withId("com.android.library") { - configureAfterEvaluate(project, extension) - } - project.plugins.withId("com.android.application") { - configureAfterEvaluate(project, extension) - } + val BUN_INSTALL = project.rootProject.file(".bun") - project.afterEvaluate { - if (!project.plugins.hasPlugin("com.android.library") - && !project.plugins.hasPlugin("com.android.application") - ) { - configure(project, extension) - } - } - } + val generatedAssetsDir = File( + project.layout.buildDirectory.get().asFile, + "/generated/sources/frontend/assets", + ) - private fun configureAfterEvaluate(project: Project, extension: SvelteAssetsPluginExtension) { - project.afterEvaluate { configure(project, extension) } - } + val installBunLocally = project.tasks.register("installBunLocally") - private fun configure(project: Project, extension: SvelteAssetsPluginExtension) { - val isWindows = System.getProperty("os.name").lowercase().contains("windows") - val webDir = File(project.projectDir, extension.webAppPath) - val outputDir = File(project.projectDir, "src/main/assets/${extension.assetsPath}") + project.tasks.register("uninstallBun", Delete::class.java) { + group = "frontend" + description = "Removes the project's local bun installation." + delete(BUN_INSTALL) + } + + val clearFrontendAssets = + project.tasks.register("clearFrontendAssets", Delete::class.java) { + group = "frontend" + description = "Clears frontend assets." - val singleSlot = project.gradle.sharedServices - .registerIfAbsent("svelteSingleSlot", SvelteSingleSlotService::class.java) { - maxParallelUsages.set(extension.maxParallelSvelteBuilds.coerceAtLeast(1)) + delete(generatedAssetsDir) } - val projectSuffix = project.name.replace(Regex("\\W+"), "") - .replaceFirstChar { if (it.isLowerCase()) it.titlecase() else it.toString() } + val bunBuild = project.tasks.register("bunBuild") { + val webDir = project.file(extension.webAppPath) - val installCmd = when { - extension.useNpm && isWindows -> listOf("cmd", "/c", "npm", "i") - extension.useNpm -> listOf("sh", "-c", "npm i") - !extension.useNpm && isWindows -> listOf("cmd", "/c", "bun", "i") - else -> listOf("sh", "-c", "bun i") - } - val buildCmd = when { - extension.useNpm && isWindows -> listOf("cmd", "/c", "npm", "run", "build") - extension.useNpm -> listOf("sh", "-c", "npm run build") - !extension.useNpm && isWindows -> listOf("cmd", "/c", "bun", "run", "build") - else -> listOf("sh", "-c", "bun run build") - } + group = "frontend" + description = "Installs frontend dependencies, then builds the frontend." - val clearSvelte = project.tasks.register("clearSvelteAssets$projectSuffix", Delete::class.java) { - group = "svelte" - description = "Clears web assets for ${project.path}" - val pluginsDir1 = File(project.projectDir, "src/main/assets/plugins") - if (pluginsDir1.exists()) delete(pluginsDir1.listFiles()?.toList() ?: emptyList()) - val pluginsDir2 = File(project.projectDir, "src/main/assets/${extension.assetsPath}") - if (pluginsDir2.exists()) delete(pluginsDir2.listFiles()?.toList() ?: emptyList()) - } + dependsOn(installBunLocally) - val installSvelteApp = project.tasks.register("installSvelteApp$projectSuffix", Exec::class.java) { - group = "svelte" - description = "Installs web dependencies for ${project.path} (${if (extension.useNpm) "npm" else "bun"})" - dependsOn(clearSvelte) - workingDir = webDir - commandLine = installCmd - isIgnoreExitValue = true - usesService(singleSlot) - } - attachExecLogging(project, installSvelteApp, "install") - - val buildSvelteApp = project.tasks.register("buildSvelteApp$projectSuffix", Exec::class.java) { - group = "svelte" - description = "Builds the Svelte app for ${project.path}" - dependsOn(installSvelteApp) - workingDir = webDir - commandLine = buildCmd - isIgnoreExitValue = true - usesService(singleSlot) - } - attachExecLogging(project, buildSvelteApp, "build") - - val copySvelteToAssets = project.tasks.register("copySvelteToAssets$projectSuffix", Copy::class.java) { - group = "svelte" - description = "Copies the built Svelte assets into ${outputDir.relativeTo(project.projectDir)} for ${project.path}" - dependsOn(buildSvelteApp) - from(File(webDir, extension.buildDirPath)) - into(outputDir) - } + val inputFiles = project.fileTree(webDir) { + exclude(extension.buildDirPath) + exclude("node_modules") + exclude(".panels") + exclude(".svelte-kit") + } - val buildSvelteAggregate = project.tasks.register("buildSvelte$projectSuffix") { - group = "svelte" - description = "Installs, builds, and copies Svelte assets for ${project.path}" - dependsOn(copySvelteToAssets) - usesService(singleSlot) - } + inputs.dir(inputFiles) - val root = project.rootProject - val rootTask = root.tasks.findByName("buildAllSvelte") - ?: root.tasks.create("buildAllSvelte") { - group = "svelte" - description = "Builds Svelte assets for all subprojects" - } - rootTask.dependsOn(buildSvelteAggregate) - - val extra = root.extensions.extraProperties - val keyAggregates = "svelteAggregates" - val keyWired = "svelteAggregatesWired" - - @Suppress("UNCHECKED_CAST") - val aggregates: MutableList> = - if (extra.has(keyAggregates)) { - extra.get(keyAggregates) as MutableList> - } else { - mutableListOf>().also { - extra.set(keyAggregates, it) - } - } - aggregates.add(buildSvelteAggregate) - - if (!extra.has(keyWired)) { - extra.set(keyWired, true) - root.gradle.projectsEvaluated { - @Suppress("UNCHECKED_CAST") - val allProviders = (extra.get(keyAggregates) - as MutableList>).toList() - if (allProviders.isNotEmpty()) { - val allTasks = allProviders.map { it.get() }.sortedWith( - compareBy { it.project.path }.thenBy { it.name } - ) - for (i in 1 until allTasks.size) { - allTasks[i].dependsOn(allTasks[i - 1]) - } - rootTask.setDependsOn(listOf(allTasks.last())) - } - root.tasks.matching { it.name == "publish" }.configureEach { - dependsOn(rootTask) + outputs.dir(File(webDir, extension.buildDirPath)) + outputs.dir(File(webDir, "node_modules")) + outputs.dir(File(webDir, ".panels")) + outputs.dir(File(webDir, ".svelte-kit")) + + doLast { + bunExec("bun i && bun run build") { + workingDir = webDir } } } - } - private fun attachExecLogging( - project: Project, - taskProvider: org.gradle.api.tasks.TaskProvider, - label: String - ) { - taskProvider.configure { - val relProjectPath = project.path.removePrefix(":").ifEmpty { "root" } - val safePath = relProjectPath.replace(':', '_') - val logsDir = File(project.buildDir, "svelte-logs/$safePath").also { it.mkdirs() } - - val timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd-HHmmss")) - val latestLog = File(logsDir, "$label.log") - val tsLog = File(logsDir, "$label-$timestamp.log") - - doFirst { - logsDir.mkdirs() - project.logger.log( - LogLevel.LIFECYCLE, - "[svelte] ${project.path} ${name}: logging to ${latestLog.relativeTo(project.projectDir)} (and ${tsLog.name})" + val copyFrontendToAssets = + project.tasks.register("copyFrontendToAssets", Copy::class.java) { + val assetsPath = requireNotNull(extension.assetsPath) { "assets path must be set" } + val outputDir = File( + generatedAssetsDir, + assetsPath, ) - val fout = FileOutputStream(latestLog, false) - val fts = FileOutputStream(tsLog, false) - val fileTee = TeeOutputStream(fout, fts) - val stdoutTee = TeeOutputStream(StdOutStream, fileTee) - val stderrTee = TeeOutputStream(StdErrStream, fileTee) - standardOutput = stdoutTee - errorOutput = stderrTee - val header = "===== ${project.path} :: $name ($label) :: $timestamp =====\n" - stdoutTee.write(header.toByteArray()) - stdoutTee.flush() + val webDir = project.file(extension.webAppPath) + + group = "frontend" + description = + "Copies the built frontend assets into ${outputDir.relativeTo(project.projectDir)}." + + dependsOn(clearFrontendAssets) + dependsOn(bunBuild) + + from(File(webDir, extension.buildDirPath)) + into(outputDir) } - doLast { - val result = executionResult.get() - val exit = result.exitValue - if (exit != 0) { - val tailLines = 200 - val lines = if (latestLog.exists()) latestLog.readLines() else emptyList() - val tail = lines.takeLast(tailLines) - project.logger.error( - "\n\u001B[31m[svelte] ${project.path} ${name} failed (exit $exit). " + - "Showing last $tailLines log lines from ${latestLog.relativeTo(project.projectDir)}:\u001B[0m" - ) - tail.forEach { project.logger.error(it) } - throw GradleException( - "[svelte] ${project.path} ${name} failed. Full log: ${latestLog.absolutePath}" - ) - } + val buildFrontend = project.tasks.register("buildFrontend") { + group = "frontend" + description = "Builds frontend assets for bundling into the library." + + dependsOn(copyFrontendToAssets) + + outputs.dir(generatedAssetsDir) + } + + project.extensions.getByType(LibraryAndroidComponentsExtension::class.java).finalizeDsl { + it.sourceSets.getByName("main") { + assets.srcDir(generatedAssetsDir) } } + + project.tasks.named("preBuild").dependsOn(buildFrontend) } } diff --git a/library/plugin-svelte-assets/src/main/kotlin/com/bylazar/SvelteAssetsPluginExtension.kt b/library/plugin-svelte-assets/src/main/kotlin/com/bylazar/SvelteAssetsPluginExtension.kt new file mode 100644 index 0000000..4a0fa87 --- /dev/null +++ b/library/plugin-svelte-assets/src/main/kotlin/com/bylazar/SvelteAssetsPluginExtension.kt @@ -0,0 +1,8 @@ +package com.bylazar + +abstract class SvelteAssetsPluginExtension { + fun assetPathForPlugin(pluginNamespace: String) = "web/plugins/$pluginNamespace" + var assetsPath: String? = null + var webAppPath: String = "web" + var buildDirPath: String = "dist" +} \ No newline at end of file diff --git a/library/plugin-svelte-assets/src/main/kotlin/com/bylazar/tasks/BunTask.kt b/library/plugin-svelte-assets/src/main/kotlin/com/bylazar/tasks/BunTask.kt new file mode 100644 index 0000000..9ad4c61 --- /dev/null +++ b/library/plugin-svelte-assets/src/main/kotlin/com/bylazar/tasks/BunTask.kt @@ -0,0 +1,46 @@ +package com.bylazar.tasks + +import org.gradle.api.DefaultTask +import org.gradle.api.tasks.CacheableTask +import org.gradle.api.tasks.Internal +import org.gradle.process.ExecSpec +import java.io.File + +@CacheableTask +abstract class BunTask : DefaultTask() { + companion object { + val isWindows = System.getProperty("os.name").lowercase().contains("windows") + } + + @Suppress("PropertyName") + @get:Internal + val BUN_INSTALL = project.rootProject.file(".bun") + + @get:Internal + val bunInstalledLocally + get() = (isWindows && File(BUN_INSTALL, "bin\\bun.exe").exists())// + || (!isWindows && File(BUN_INSTALL, "bin/bun").exists()) + + @get:Internal + val bunInstalled + get() = System.getenv("BUN_INSTALL") != null || bunInstalledLocally + + /** + * executes [cmd] under powershell or sh, + * with the appropriate env vars set to use the locally installed bun + * if necessary + */ + fun bunExec(cmd: String, action: ExecSpec.() -> Unit = {}) { + project.exec { + commandLine = if (isWindows) listOf("powershell", "-c", cmd) + else listOf("sh", "-c", cmd) + if (bunInstalledLocally) { + environment["BUN_INSTALL"] = BUN_INSTALL.absolutePath + environment["PATH"] = + if (isWindows) "${BUN_INSTALL.absolutePath}\\bin;${System.getenv("PATH")}" + else "${BUN_INSTALL.absolutePath}/bin:${System.getenv("PATH")}" + } + action() + } + } +} \ No newline at end of file diff --git a/library/plugin-svelte-assets/src/main/kotlin/com/bylazar/tasks/InstallBunLocally.kt b/library/plugin-svelte-assets/src/main/kotlin/com/bylazar/tasks/InstallBunLocally.kt new file mode 100644 index 0000000..29ddb79 --- /dev/null +++ b/library/plugin-svelte-assets/src/main/kotlin/com/bylazar/tasks/InstallBunLocally.kt @@ -0,0 +1,21 @@ +package com.bylazar.tasks + +import org.gradle.api.tasks.TaskAction + +abstract class InstallBunLocally : BunTask() { + init { + group = "frontend" + onlyIf { !bunInstalled } + } + + @TaskAction + fun installBun() { + project.exec { + commandLine = if (isWindows) listOf("powershell", "-c", "irm bun.sh/install.ps1|iex") + else listOf("sh", "-c", "curl -fsSL https://bun.com/install | bash\n") + environment["BUN_INSTALL"] = BUN_INSTALL.absolutePath + standardOutput = System.out + errorOutput = System.err + } + } +} \ No newline at end of file From 524de9b798839072d1e7d27be6dda65718eace6e Mon Sep 17 00:00:00 2001 From: Oscar Chevalier Date: Sun, 11 Jan 2026 06:27:53 +1100 Subject: [PATCH 07/15] chore: switch to dairy publishing --- library/Battery/build.gradle.kts | 33 +++++++++++++++++-------- library/CameraStream/build.gradle.kts | 33 +++++++++++++++++-------- library/Capture/build.gradle.kts | 33 +++++++++++++++++-------- library/Configurables/build.gradle.kts | 21 +++++++++++++--- library/Docs/build.gradle.kts | 33 +++++++++++++++++-------- library/ExamplePlugin/build.gradle.kts | 33 +++++++++++++++++-------- library/Field/build.gradle.kts | 33 +++++++++++++++++-------- library/FullPanels/build.gradle.kts | 33 +++++++++++++++++-------- library/Gamepad/build.gradle.kts | 33 +++++++++++++++++-------- library/Graph/build.gradle.kts | 33 +++++++++++++++++-------- library/Lights/build.gradle.kts | 33 +++++++++++++++++-------- library/LimelightProxy/build.gradle.kts | 33 +++++++++++++++++-------- library/OpModeControl/build.gradle.kts | 21 +++++++++++++--- library/Panels/build.gradle.kts | 33 +++++++++++++++++-------- library/Pinger/build.gradle.kts | 33 +++++++++++++++++-------- library/Telemetry/build.gradle.kts | 33 +++++++++++++++++-------- library/Themes/build.gradle.kts | 33 +++++++++++++++++-------- library/Utils/build.gradle.kts | 33 +++++++++++++++++-------- 18 files changed, 402 insertions(+), 168 deletions(-) diff --git a/library/Battery/build.gradle.kts b/library/Battery/build.gradle.kts index 81b0765..73fe956 100644 --- a/library/Battery/build.gradle.kts +++ b/library/Battery/build.gradle.kts @@ -4,14 +4,32 @@ val pluginVersion = "1.0.3" plugins { id("com.android.library") id("org.jetbrains.kotlin.android") - id("maven-publish") id("com.bylazar.svelte-assets") + id("dev.frozenmilk.publish") version "0.0.5" + id("dev.frozenmilk.doc") version "0.0.5" + id("dev.frozenmilk.build-meta-data") version "0.0.2" } svelteAssets { assetsPath = assetPathForPlugin(pluginNamespace) } +dairyPublishing { + gitDir = file("..") +} + +version = "${dairyPublishing.version}+$pluginVersion" + +meta { + packagePath = pluginNamespace + name = "Battery" + registerField("name", "String", "\"$pluginNamespace\"") + registerField("clean", "Boolean") { "${dairyPublishing.clean}" } + registerField("gitRef", "String") { "\"${dairyPublishing.gitRef}\"" } + registerField("snapshot", "Boolean") { "${dairyPublishing.snapshot}" } + registerField("version", "String") { "\"$version\"" } +} + android { namespace = pluginNamespace @@ -59,9 +77,11 @@ afterEvaluate { create("release") { from(components["release"]) - groupId = pluginNamespace.substringBeforeLast('.') + groupId = pluginNamespace.substringBeforeLast('.') + ".sloth" artifactId = pluginNamespace.substringAfterLast('.') - version = pluginVersion + + artifact(dairyDoc.dokkaJavadocJar) + artifact(dairyDoc.dokkaHtmlJar) pom { description.set("Panels Battery Plugin") @@ -78,12 +98,5 @@ afterEvaluate { } } } - - repositories { - maven { - name = "localDevRepo" - url = uri("file:///C:/Users/lazar/Documents/GitHub/ftcontrol-maven/releases") - } - } } } diff --git a/library/CameraStream/build.gradle.kts b/library/CameraStream/build.gradle.kts index efa462b..bfe297d 100644 --- a/library/CameraStream/build.gradle.kts +++ b/library/CameraStream/build.gradle.kts @@ -4,14 +4,32 @@ val pluginVersion = "1.0.0" plugins { id("com.android.library") id("org.jetbrains.kotlin.android") - id("maven-publish") id("com.bylazar.svelte-assets") + id("dev.frozenmilk.publish") version "0.0.5" + id("dev.frozenmilk.doc") version "0.0.5" + id("dev.frozenmilk.build-meta-data") version "0.0.2" } svelteAssets { assetsPath = assetPathForPlugin(pluginNamespace) } +dairyPublishing { + gitDir = file("..") +} + +version = "${dairyPublishing.version}+$pluginVersion" + +meta { + packagePath = pluginNamespace + name = "CameraStream" + registerField("name", "String", "\"$pluginNamespace\"") + registerField("clean", "Boolean") { "${dairyPublishing.clean}" } + registerField("gitRef", "String") { "\"$version\"" } + registerField("snapshot", "Boolean") { "${dairyPublishing.snapshot}" } + registerField("version", "String") { "\"${dairyPublishing.version}\"" } +} + android { namespace = pluginNamespace @@ -60,9 +78,11 @@ afterEvaluate { create("release") { from(components["release"]) - groupId = pluginNamespace.substringBeforeLast('.') + groupId = pluginNamespace.substringBeforeLast('.') + ".sloth" artifactId = pluginNamespace.substringAfterLast('.') - version = pluginVersion + + artifact(dairyDoc.dokkaJavadocJar) + artifact(dairyDoc.dokkaHtmlJar) pom { description.set("Panels Camera Stream Plugin") @@ -79,12 +99,5 @@ afterEvaluate { } } } - - repositories { - maven { - name = "localDevRepo" - url = uri("file:///C:/Users/lazar/Documents/GitHub/ftcontrol-maven/releases") - } - } } } diff --git a/library/Capture/build.gradle.kts b/library/Capture/build.gradle.kts index 490c80c..f9b9759 100644 --- a/library/Capture/build.gradle.kts +++ b/library/Capture/build.gradle.kts @@ -4,14 +4,32 @@ val pluginVersion = "1.0.3" plugins { id("com.android.library") id("org.jetbrains.kotlin.android") - id("maven-publish") id("com.bylazar.svelte-assets") + id("dev.frozenmilk.publish") version "0.0.5" + id("dev.frozenmilk.doc") version "0.0.5" + id("dev.frozenmilk.build-meta-data") version "0.0.2" } svelteAssets { assetsPath = assetPathForPlugin(pluginNamespace) } +dairyPublishing { + gitDir = file("..") +} + +version = "${dairyPublishing.version}+$pluginVersion" + +meta { + packagePath = pluginNamespace + name = "Capture" + registerField("name", "String", "\"$pluginNamespace\"") + registerField("clean", "Boolean") { "${dairyPublishing.clean}" } + registerField("gitRef", "String") { "\"${dairyPublishing.gitRef}\"" } + registerField("snapshot", "Boolean") { "${dairyPublishing.snapshot}" } + registerField("version", "String") { "\"$version\"" } +} + android { namespace = pluginNamespace @@ -59,9 +77,11 @@ afterEvaluate { create("release") { from(components["release"]) - groupId = pluginNamespace.substringBeforeLast('.') + groupId = pluginNamespace.substringBeforeLast('.') + ".sloth" artifactId = pluginNamespace.substringAfterLast('.') - version = pluginVersion + + artifact(dairyDoc.dokkaJavadocJar) + artifact(dairyDoc.dokkaHtmlJar) pom { description.set("Panels Capture Plugin") @@ -78,12 +98,5 @@ afterEvaluate { } } } - - repositories { - maven { - name = "localDevRepo" - url = uri("file:///C:/Users/lazar/Documents/GitHub/ftcontrol-maven/releases") - } - } } } diff --git a/library/Configurables/build.gradle.kts b/library/Configurables/build.gradle.kts index 678b626..c9a57c5 100644 --- a/library/Configurables/build.gradle.kts +++ b/library/Configurables/build.gradle.kts @@ -7,12 +7,29 @@ plugins { id("com.bylazar.svelte-assets") id("dev.frozenmilk.publish") version "0.0.5" id("dev.frozenmilk.doc") version "0.0.5" + id("dev.frozenmilk.build-meta-data") version "0.0.2" } svelteAssets { assetsPath = assetPathForPlugin(pluginNamespace) } +dairyPublishing { + gitDir = file("..") +} + +version = "${dairyPublishing.version}+$pluginVersion" + +meta { + packagePath = pluginNamespace + name = "Configurables" + registerField("name", "String", "\"$pluginNamespace\"") + registerField("clean", "Boolean") { "${dairyPublishing.clean}" } + registerField("gitRef", "String") { "\"${dairyPublishing.gitRef}\"" } + registerField("snapshot", "Boolean") { "${dairyPublishing.snapshot}" } + registerField("version", "String") { "\"${dairyPublishing.version}\"" } +} + android { namespace = pluginNamespace @@ -41,10 +58,6 @@ android { } } -dairyPublishing { - gitDir = file("..") -} - repositories { maven("https://repo.dairy.foundation/releases") } diff --git a/library/Docs/build.gradle.kts b/library/Docs/build.gradle.kts index aea7749..3611171 100644 --- a/library/Docs/build.gradle.kts +++ b/library/Docs/build.gradle.kts @@ -4,14 +4,32 @@ val pluginVersion = "1.0.5" plugins { id("com.android.library") id("org.jetbrains.kotlin.android") - id("maven-publish") id("com.bylazar.svelte-assets") + id("dev.frozenmilk.publish") version "0.0.5" + id("dev.frozenmilk.doc") version "0.0.5" + id("dev.frozenmilk.build-meta-data") version "0.0.2" } svelteAssets { assetsPath = assetPathForPlugin(pluginNamespace) } +dairyPublishing { + gitDir = file("..") +} + +version = "${dairyPublishing.version}+$pluginVersion" + +meta { + packagePath = pluginNamespace + name = "Docs" + registerField("name", "String", "\"$pluginNamespace\"") + registerField("clean", "Boolean") { "${dairyPublishing.clean}" } + registerField("gitRef", "String") { "\"${dairyPublishing.gitRef}\"" } + registerField("snapshot", "Boolean") { "${dairyPublishing.snapshot}" } + registerField("version", "String") { "\"$version\"" } +} + android { namespace = pluginNamespace @@ -59,9 +77,11 @@ afterEvaluate { create("release") { from(components["release"]) - groupId = pluginNamespace.substringBeforeLast('.') + groupId = pluginNamespace.substringBeforeLast('.') + ".sloth" artifactId = pluginNamespace.substringAfterLast('.') - version = pluginVersion + + artifact(dairyDoc.dokkaJavadocJar) + artifact(dairyDoc.dokkaHtmlJar) pom { description.set("Panels Docs Plugin") @@ -78,12 +98,5 @@ afterEvaluate { } } } - - repositories { - maven { - name = "localDevRepo" - url = uri("file:///C:/Users/lazar/Documents/GitHub/ftcontrol-maven/releases") - } - } } } diff --git a/library/ExamplePlugin/build.gradle.kts b/library/ExamplePlugin/build.gradle.kts index 82f496f..b2fa8eb 100644 --- a/library/ExamplePlugin/build.gradle.kts +++ b/library/ExamplePlugin/build.gradle.kts @@ -4,14 +4,32 @@ val pluginVersion = "0.0.14" plugins { id("com.android.library") id("org.jetbrains.kotlin.android") - id("maven-publish") id("com.bylazar.svelte-assets") + id("dev.frozenmilk.publish") version "0.0.5" + id("dev.frozenmilk.doc") version "0.0.5" + id("dev.frozenmilk.build-meta-data") version "0.0.2" } svelteAssets { assetsPath = assetPathForPlugin(pluginNamespace) } +dairyPublishing { + gitDir = file("..") +} + +version = "${dairyPublishing.version}+$pluginVersion" + +meta { + packagePath = pluginNamespace + name = "Example" + registerField("name", "String", "\"$pluginNamespace\"") + registerField("clean", "Boolean") { "${dairyPublishing.clean}" } + registerField("gitRef", "String") { "\"${dairyPublishing.gitRef}\"" } + registerField("snapshot", "Boolean") { "${dairyPublishing.snapshot}" } + registerField("version", "String") { "\"$version\"" } +} + android { namespace = pluginNamespace @@ -59,9 +77,11 @@ afterEvaluate { create("release") { from(components["release"]) - groupId = pluginNamespace.substringBeforeLast('.') + groupId = pluginNamespace.substringBeforeLast('.') + ".sloth" artifactId = pluginNamespace.substringAfterLast('.') - version = pluginVersion + + artifact(dairyDoc.dokkaJavadocJar) + artifact(dairyDoc.dokkaHtmlJar) pom { description.set("Panels Example Plugin") @@ -78,12 +98,5 @@ afterEvaluate { } } } - - repositories { - maven { - name = "localDevRepo" - url = uri("file:///C:/Users/lazar/Documents/GitHub/ftcontrol-maven/releases") - } - } } } diff --git a/library/Field/build.gradle.kts b/library/Field/build.gradle.kts index 8dbebe8..f031cab 100644 --- a/library/Field/build.gradle.kts +++ b/library/Field/build.gradle.kts @@ -4,14 +4,32 @@ val pluginVersion = "1.0.6" plugins { id("com.android.library") id("org.jetbrains.kotlin.android") - id("maven-publish") id("com.bylazar.svelte-assets") + id("dev.frozenmilk.publish") version "0.0.5" + id("dev.frozenmilk.doc") version "0.0.5" + id("dev.frozenmilk.build-meta-data") version "0.0.2" } svelteAssets { assetsPath = assetPathForPlugin(pluginNamespace) } +dairyPublishing { + gitDir = file("..") +} + +version = "${dairyPublishing.version}+$pluginVersion" + +meta { + packagePath = pluginNamespace + name = "Field" + registerField("name", "String", "\"$pluginNamespace\"") + registerField("clean", "Boolean") { "${dairyPublishing.clean}" } + registerField("gitRef", "String") { "\"${dairyPublishing.gitRef}\"" } + registerField("snapshot", "Boolean") { "${dairyPublishing.snapshot}" } + registerField("version", "String") { "\"$version\"" } +} + android { namespace = pluginNamespace @@ -59,9 +77,11 @@ afterEvaluate { create("release") { from(components["release"]) - groupId = pluginNamespace.substringBeforeLast('.') + groupId = pluginNamespace.substringBeforeLast('.') + ".sloth" artifactId = pluginNamespace.substringAfterLast('.') - version = pluginVersion + + artifact(dairyDoc.dokkaJavadocJar) + artifact(dairyDoc.dokkaHtmlJar) pom { description.set("Panels Field Plugin") @@ -78,12 +98,5 @@ afterEvaluate { } } } - - repositories { - maven { - name = "localDevRepo" - url = uri("file:///C:/Users/lazar/Documents/GitHub/ftcontrol-maven/releases") - } - } } } diff --git a/library/FullPanels/build.gradle.kts b/library/FullPanels/build.gradle.kts index cebdef2..97ca274 100644 --- a/library/FullPanels/build.gradle.kts +++ b/library/FullPanels/build.gradle.kts @@ -4,14 +4,32 @@ val pluginVersion = "1.0.12" plugins { id("com.android.library") id("org.jetbrains.kotlin.android") - id("maven-publish") id("com.bylazar.svelte-assets") + id("dev.frozenmilk.publish") version "0.0.5" + id("dev.frozenmilk.doc") version "0.0.5" + id("dev.frozenmilk.build-meta-data") version "0.0.2" } svelteAssets { assetsPath = assetPathForPlugin(pluginNamespace) } +dairyPublishing { + gitDir = file("..") +} + +version = "${dairyPublishing.version}+$pluginVersion" + +meta { + packagePath = pluginNamespace + name = "FullPanels" + registerField("name", "String", "\"$pluginNamespace\"") + registerField("clean", "Boolean") { "${dairyPublishing.clean}" } + registerField("gitRef", "String") { "\"${dairyPublishing.gitRef}\"" } + registerField("snapshot", "Boolean") { "${dairyPublishing.snapshot}" } + registerField("version", "String") { "\"$version\"" } +} + android { namespace = pluginNamespace @@ -79,9 +97,11 @@ afterEvaluate { create("release") { from(components["release"]) - groupId = pluginNamespace.substringBeforeLast('.') + groupId = pluginNamespace.substringBeforeLast('.') + ".sloth" artifactId = pluginNamespace.substringAfterLast('.') - version = pluginVersion + + artifact(dairyDoc.dokkaJavadocJar) + artifact(dairyDoc.dokkaHtmlJar) pom { description.set("All in one toolbox dashboard for FTC with core plugins.") @@ -98,12 +118,5 @@ afterEvaluate { } } } - - repositories { - maven { - name = "localDevRepo" - url = uri("file:///C:/Users/lazar/Documents/GitHub/ftcontrol-maven/releases") - } - } } } diff --git a/library/Gamepad/build.gradle.kts b/library/Gamepad/build.gradle.kts index f32fbdf..a89a37a 100644 --- a/library/Gamepad/build.gradle.kts +++ b/library/Gamepad/build.gradle.kts @@ -4,14 +4,32 @@ val pluginVersion = "1.0.4" plugins { id("com.android.library") id("org.jetbrains.kotlin.android") - id("maven-publish") id("com.bylazar.svelte-assets") + id("dev.frozenmilk.publish") version "0.0.5" + id("dev.frozenmilk.doc") version "0.0.5" + id("dev.frozenmilk.build-meta-data") version "0.0.2" } svelteAssets { assetsPath = assetPathForPlugin(pluginNamespace) } +dairyPublishing { + gitDir = file("..") +} + +version = "${dairyPublishing.version}+$pluginVersion" + +meta { + packagePath = pluginNamespace + name = "Gamepad" + registerField("name", "String", "\"$pluginNamespace\"") + registerField("clean", "Boolean") { "${dairyPublishing.clean}" } + registerField("gitRef", "String") { "\"${dairyPublishing.gitRef}\"" } + registerField("snapshot", "Boolean") { "${dairyPublishing.snapshot}" } + registerField("version", "String") { "\"$version\"" } +} + android { namespace = pluginNamespace @@ -59,9 +77,11 @@ afterEvaluate { create("release") { from(components["release"]) - groupId = pluginNamespace.substringBeforeLast('.') + groupId = pluginNamespace.substringBeforeLast('.') + ".sloth" artifactId = pluginNamespace.substringAfterLast('.') - version = pluginVersion + + artifact(dairyDoc.dokkaJavadocJar) + artifact(dairyDoc.dokkaHtmlJar) pom { description.set("Panels Gamepad Plugin") @@ -78,12 +98,5 @@ afterEvaluate { } } } - - repositories { - maven { - name = "localDevRepo" - url = uri("file:///C:/Users/lazar/Documents/GitHub/ftcontrol-maven/releases") - } - } } } diff --git a/library/Graph/build.gradle.kts b/library/Graph/build.gradle.kts index 8b5bc7c..0dcd2ec 100644 --- a/library/Graph/build.gradle.kts +++ b/library/Graph/build.gradle.kts @@ -4,14 +4,32 @@ val pluginVersion = "1.0.4" plugins { id("com.android.library") id("org.jetbrains.kotlin.android") - id("maven-publish") id("com.bylazar.svelte-assets") + id("dev.frozenmilk.publish") version "0.0.5" + id("dev.frozenmilk.doc") version "0.0.5" + id("dev.frozenmilk.build-meta-data") version "0.0.2" } svelteAssets { assetsPath = assetPathForPlugin(pluginNamespace) } +dairyPublishing { + gitDir = file("..") +} + +version = "${dairyPublishing.version}+$pluginVersion" + +meta { + packagePath = pluginNamespace + name = "Graph" + registerField("name", "String", "\"$pluginNamespace\"") + registerField("clean", "Boolean") { "${dairyPublishing.clean}" } + registerField("gitRef", "String") { "\"${dairyPublishing.gitRef}\"" } + registerField("snapshot", "Boolean") { "${dairyPublishing.snapshot}" } + registerField("version", "String") { "\"$version\"" } +} + android { namespace = pluginNamespace @@ -59,9 +77,11 @@ afterEvaluate { create("release") { from(components["release"]) - groupId = pluginNamespace.substringBeforeLast('.') + groupId = pluginNamespace.substringBeforeLast('.') + ".sloth" artifactId = pluginNamespace.substringAfterLast('.') - version = pluginVersion + + artifact(dairyDoc.dokkaJavadocJar) + artifact(dairyDoc.dokkaHtmlJar) pom { description.set("Panels Graph Plugin") @@ -78,12 +98,5 @@ afterEvaluate { } } } - - repositories { - maven { - name = "localDevRepo" - url = uri("file:///C:/Users/lazar/Documents/GitHub/ftcontrol-maven/releases") - } - } } } diff --git a/library/Lights/build.gradle.kts b/library/Lights/build.gradle.kts index 7416d31..2a53a34 100644 --- a/library/Lights/build.gradle.kts +++ b/library/Lights/build.gradle.kts @@ -4,14 +4,32 @@ val pluginVersion = "1.0.3" plugins { id("com.android.library") id("org.jetbrains.kotlin.android") - id("maven-publish") id("com.bylazar.svelte-assets") + id("dev.frozenmilk.publish") version "0.0.5" + id("dev.frozenmilk.doc") version "0.0.5" + id("dev.frozenmilk.build-meta-data") version "0.0.2" } svelteAssets { assetsPath = assetPathForPlugin(pluginNamespace) } +dairyPublishing { + gitDir = file("..") +} + +version = "${dairyPublishing.version}+$pluginVersion" + +meta { + packagePath = pluginNamespace + name = "Lights" + registerField("name", "String", "\"$pluginNamespace\"") + registerField("clean", "Boolean") { "${dairyPublishing.clean}" } + registerField("gitRef", "String") { "\"${dairyPublishing.gitRef}\"" } + registerField("snapshot", "Boolean") { "${dairyPublishing.snapshot}" } + registerField("version", "String") { "\"$version\"" } +} + android { namespace = pluginNamespace @@ -59,9 +77,11 @@ afterEvaluate { create("release") { from(components["release"]) - groupId = pluginNamespace.substringBeforeLast('.') + groupId = pluginNamespace.substringBeforeLast('.') + ".sloth" artifactId = pluginNamespace.substringAfterLast('.') - version = pluginVersion + + artifact(dairyDoc.dokkaJavadocJar) + artifact(dairyDoc.dokkaHtmlJar) pom { description.set("Panels Telemetry Plugin") @@ -78,12 +98,5 @@ afterEvaluate { } } } - - repositories { - maven { - name = "localDevRepo" - url = uri("file:///C:/Users/lazar/Documents/GitHub/ftcontrol-maven/releases") - } - } } } diff --git a/library/LimelightProxy/build.gradle.kts b/library/LimelightProxy/build.gradle.kts index fbeb14e..a59e899 100644 --- a/library/LimelightProxy/build.gradle.kts +++ b/library/LimelightProxy/build.gradle.kts @@ -4,14 +4,32 @@ val pluginVersion = "1.0.4" plugins { id("com.android.library") id("org.jetbrains.kotlin.android") - id("maven-publish") id("com.bylazar.svelte-assets") + id("dev.frozenmilk.publish") version "0.0.5" + id("dev.frozenmilk.doc") version "0.0.5" + id("dev.frozenmilk.build-meta-data") version "0.0.2" } svelteAssets { assetsPath = assetPathForPlugin(pluginNamespace) } +dairyPublishing { + gitDir = file("..") +} + +version = "${dairyPublishing.version}+$pluginVersion" + +meta { + packagePath = pluginNamespace + name = "LimelightProxy" + registerField("name", "String", "\"$pluginNamespace\"") + registerField("clean", "Boolean") { "${dairyPublishing.clean}" } + registerField("gitRef", "String") { "\"$version\"" } + registerField("snapshot", "Boolean") { "${dairyPublishing.snapshot}" } + registerField("version", "String") { "\"${dairyPublishing.version}\"" } +} + android { namespace = pluginNamespace @@ -62,9 +80,11 @@ afterEvaluate { create("release") { from(components["release"]) - groupId = pluginNamespace.substringBeforeLast('.') + groupId = pluginNamespace.substringBeforeLast('.') + ".sloth" artifactId = pluginNamespace.substringAfterLast('.') - version = pluginVersion + + artifact(dairyDoc.dokkaJavadocJar) + artifact(dairyDoc.dokkaHtmlJar) pom { description.set("Panels Limelight Proxy Plugin") @@ -81,12 +101,5 @@ afterEvaluate { } } } - - repositories { - maven { - name = "localDevRepo" - url = uri("file:///C:/Users/lazar/Documents/GitHub/ftcontrol-maven/releases") - } - } } } diff --git a/library/OpModeControl/build.gradle.kts b/library/OpModeControl/build.gradle.kts index 922d8e2..b112a13 100644 --- a/library/OpModeControl/build.gradle.kts +++ b/library/OpModeControl/build.gradle.kts @@ -7,12 +7,29 @@ plugins { id("com.bylazar.svelte-assets") id("dev.frozenmilk.publish") version "0.0.5" id("dev.frozenmilk.doc") version "0.0.5" + id("dev.frozenmilk.build-meta-data") version "0.0.2" } svelteAssets { assetsPath = assetPathForPlugin(pluginNamespace) } +dairyPublishing { + gitDir = file("..") +} + +version = "${dairyPublishing.version}+$pluginVersion" + +meta { + packagePath = pluginNamespace + name = "OpModeControl" + registerField("name", "String", "\"$pluginNamespace\"") + registerField("clean", "Boolean") { "${dairyPublishing.clean}" } + registerField("gitRef", "String") { "\"${dairyPublishing.gitRef}\"" } + registerField("snapshot", "Boolean") { "${dairyPublishing.snapshot}" } + registerField("version", "String") { "\"${dairyPublishing.version}\"" } +} + android { namespace = pluginNamespace @@ -41,10 +58,6 @@ android { } } -dairyPublishing { - gitDir = file("..") -} - repositories { maven("https://repo.dairy.foundation/releases") } diff --git a/library/Panels/build.gradle.kts b/library/Panels/build.gradle.kts index 5d851a3..70782a0 100644 --- a/library/Panels/build.gradle.kts +++ b/library/Panels/build.gradle.kts @@ -4,8 +4,10 @@ val pluginVersion = "1.0.5" plugins { id("com.android.library") id("org.jetbrains.kotlin.android") - id("maven-publish") id("com.bylazar.svelte-assets") + id("dev.frozenmilk.publish") version "0.0.5" + id("dev.frozenmilk.doc") version "0.0.5" + id("dev.frozenmilk.build-meta-data") version "0.0.2" } svelteAssets { @@ -13,6 +15,22 @@ svelteAssets { buildDirPath = "build" } +dairyPublishing { + gitDir = file("..") +} + +version = "${dairyPublishing.version}+$pluginVersion" + +meta { + packagePath = pluginNamespace + name = "Panels" + registerField("name", "String", "\"$pluginNamespace\"") + registerField("clean", "Boolean") { "${dairyPublishing.clean}" } + registerField("gitRef", "String") { "\"${dairyPublishing.gitRef}\"" } + registerField("snapshot", "Boolean") { "${dairyPublishing.snapshot}" } + registerField("version", "String") { "\"$version\"" } +} + android { namespace = "com.bylazar.panels" @@ -80,9 +98,11 @@ afterEvaluate { create("release") { from(components["release"]) - groupId = pluginNamespace.substringBeforeLast('.') + groupId = pluginNamespace.substringBeforeLast('.') + ".sloth" artifactId = pluginNamespace.substringAfterLast('.') - version = pluginVersion + + artifact(dairyDoc.dokkaJavadocJar) + artifact(dairyDoc.dokkaHtmlJar) pom { description.set("All in one toolbox dashboard for FTC.") @@ -99,12 +119,5 @@ afterEvaluate { } } } - - repositories { - maven { - name = "localDevRepo" - url = uri("file:///C:/Users/lazar/Documents/GitHub/ftcontrol-maven/releases") - } - } } } diff --git a/library/Pinger/build.gradle.kts b/library/Pinger/build.gradle.kts index 871cf07..133085c 100644 --- a/library/Pinger/build.gradle.kts +++ b/library/Pinger/build.gradle.kts @@ -4,14 +4,32 @@ val pluginVersion = "1.0.3" plugins { id("com.android.library") id("org.jetbrains.kotlin.android") - id("maven-publish") id("com.bylazar.svelte-assets") + id("dev.frozenmilk.publish") version "0.0.5" + id("dev.frozenmilk.doc") version "0.0.5" + id("dev.frozenmilk.build-meta-data") version "0.0.2" } svelteAssets { assetsPath = assetPathForPlugin(pluginNamespace) } +dairyPublishing { + gitDir = file("..") +} + +version = "${dairyPublishing.version}+$pluginVersion" + +meta { + packagePath = pluginNamespace + name = "Pinger" + registerField("name", "String", "\"$pluginNamespace\"") + registerField("clean", "Boolean") { "${dairyPublishing.clean}" } + registerField("gitRef", "String") { "\"${dairyPublishing.gitRef}\"" } + registerField("snapshot", "Boolean") { "${dairyPublishing.snapshot}" } + registerField("version", "String") { "\"$version\"" } +} + android { namespace = pluginNamespace @@ -59,9 +77,11 @@ afterEvaluate { create("release") { from(components["release"]) - groupId = pluginNamespace.substringBeforeLast('.') + groupId = pluginNamespace.substringBeforeLast('.') + ".sloth" artifactId = pluginNamespace.substringAfterLast('.') - version = pluginVersion + + artifact(dairyDoc.dokkaJavadocJar) + artifact(dairyDoc.dokkaHtmlJar) pom { description.set("Panels Pinger Plugin") @@ -78,12 +98,5 @@ afterEvaluate { } } } - - repositories { - maven { - name = "localDevRepo" - url = uri("file:///C:/Users/lazar/Documents/GitHub/ftcontrol-maven/releases") - } - } } } diff --git a/library/Telemetry/build.gradle.kts b/library/Telemetry/build.gradle.kts index 7ef3dcc..7fe3633 100644 --- a/library/Telemetry/build.gradle.kts +++ b/library/Telemetry/build.gradle.kts @@ -4,14 +4,32 @@ val pluginVersion = "1.0.5" plugins { id("com.android.library") id("org.jetbrains.kotlin.android") - id("maven-publish") id("com.bylazar.svelte-assets") + id("dev.frozenmilk.publish") version "0.0.5" + id("dev.frozenmilk.doc") version "0.0.5" + id("dev.frozenmilk.build-meta-data") version "0.0.2" } svelteAssets { assetsPath = assetPathForPlugin(pluginNamespace) } +dairyPublishing { + gitDir = file("..") +} + +version = "${dairyPublishing.version}+$pluginVersion" + +meta { + packagePath = pluginNamespace + name = "Telemetry" + registerField("name", "String", "\"$pluginNamespace\"") + registerField("clean", "Boolean") { "${dairyPublishing.clean}" } + registerField("gitRef", "String") { "\"${dairyPublishing.gitRef}\"" } + registerField("snapshot", "Boolean") { "${dairyPublishing.snapshot}" } + registerField("version", "String") { "\"$version\"" } +} + android { namespace = pluginNamespace @@ -59,9 +77,11 @@ afterEvaluate { create("release") { from(components["release"]) - groupId = pluginNamespace.substringBeforeLast('.') + groupId = pluginNamespace.substringBeforeLast('.') + ".sloth" artifactId = pluginNamespace.substringAfterLast('.') - version = pluginVersion + + artifact(dairyDoc.dokkaJavadocJar) + artifact(dairyDoc.dokkaHtmlJar) pom { description.set("Panels Telemetry Plugin") @@ -78,12 +98,5 @@ afterEvaluate { } } } - - repositories { - maven { - name = "localDevRepo" - url = uri("file:///C:/Users/lazar/Documents/GitHub/ftcontrol-maven/releases") - } - } } } diff --git a/library/Themes/build.gradle.kts b/library/Themes/build.gradle.kts index 961a9e7..63c1e87 100644 --- a/library/Themes/build.gradle.kts +++ b/library/Themes/build.gradle.kts @@ -4,14 +4,32 @@ val pluginVersion = "1.0.3" plugins { id("com.android.library") id("org.jetbrains.kotlin.android") - id("maven-publish") id("com.bylazar.svelte-assets") + id("dev.frozenmilk.publish") version "0.0.5" + id("dev.frozenmilk.doc") version "0.0.5" + id("dev.frozenmilk.build-meta-data") version "0.0.2" } svelteAssets { assetsPath = assetPathForPlugin(pluginNamespace) } +dairyPublishing { + gitDir = file("..") +} + +version = "${dairyPublishing.version}+$pluginVersion" + +meta { + packagePath = pluginNamespace + name = "Themes" + registerField("name", "String", "\"$pluginNamespace\"") + registerField("clean", "Boolean") { "${dairyPublishing.clean}" } + registerField("gitRef", "String") { "\"${dairyPublishing.gitRef}\"" } + registerField("snapshot", "Boolean") { "${dairyPublishing.snapshot}" } + registerField("version", "String") { "\"$version\"" } +} + android { namespace = pluginNamespace @@ -59,9 +77,11 @@ afterEvaluate { create("release") { from(components["release"]) - groupId = pluginNamespace.substringBeforeLast('.') + groupId = pluginNamespace.substringBeforeLast('.') + ".sloth" artifactId = pluginNamespace.substringAfterLast('.') - version = pluginVersion + + artifact(dairyDoc.dokkaJavadocJar) + artifact(dairyDoc.dokkaHtmlJar) pom { description.set("Panels Themes Plugin") @@ -78,12 +98,5 @@ afterEvaluate { } } } - - repositories { - maven { - name = "localDevRepo" - url = uri("file:///C:/Users/lazar/Documents/GitHub/ftcontrol-maven/releases") - } - } } } diff --git a/library/Utils/build.gradle.kts b/library/Utils/build.gradle.kts index 12d836c..85c9afa 100644 --- a/library/Utils/build.gradle.kts +++ b/library/Utils/build.gradle.kts @@ -4,14 +4,32 @@ val pluginVersion = "1.0.4" plugins { id("com.android.library") id("org.jetbrains.kotlin.android") - id("maven-publish") id("com.bylazar.svelte-assets") + id("dev.frozenmilk.publish") version "0.0.5" + id("dev.frozenmilk.doc") version "0.0.5" + id("dev.frozenmilk.build-meta-data") version "0.0.2" } svelteAssets { assetsPath = assetPathForPlugin(pluginNamespace) } +dairyPublishing { + gitDir = file("..") +} + +version = "${dairyPublishing.version}+$pluginVersion" + +meta { + packagePath = pluginNamespace + name = "Utils" + registerField("name", "String", "\"$pluginNamespace\"") + registerField("clean", "Boolean") { "${dairyPublishing.clean}" } + registerField("gitRef", "String") { "\"${dairyPublishing.gitRef}\"" } + registerField("snapshot", "Boolean") { "${dairyPublishing.snapshot}" } + registerField("version", "String") { "\"$version\"" } +} + android { namespace = pluginNamespace @@ -61,9 +79,11 @@ afterEvaluate { create("release") { from(components["release"]) - groupId = pluginNamespace.substringBeforeLast('.') + groupId = pluginNamespace.substringBeforeLast('.') + ".sloth" artifactId = pluginNamespace.substringAfterLast('.') - version = pluginVersion + + artifact(dairyDoc.dokkaJavadocJar) + artifact(dairyDoc.dokkaHtmlJar) pom { description.set("Panels Utils Plugin") @@ -80,12 +100,5 @@ afterEvaluate { } } } - - repositories { - maven { - name = "localDevRepo" - url = uri("file:///C:/Users/lazar/Documents/GitHub/ftcontrol-maven/releases") - } - } } } From 3c588a17a74e2cd89544d69e34225b0b65390310 Mon Sep 17 00:00:00 2001 From: Oscar Chevalier Date: Sun, 11 Jan 2026 06:29:10 +1100 Subject: [PATCH 08/15] chore: adapt examples gradle to sloth --- examples/TeamCode/build.gradle | 3 +++ examples/gradle.properties | 5 ++++- examples/settings.gradle.kts | 1 + 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/examples/TeamCode/build.gradle b/examples/TeamCode/build.gradle index ffd499b..87d4ea2 100644 --- a/examples/TeamCode/build.gradle +++ b/examples/TeamCode/build.gradle @@ -38,6 +38,9 @@ android { } repositories { + maven { + url "https://repo.dairy.foundation/releases" + } } dependencies { diff --git a/examples/gradle.properties b/examples/gradle.properties index f5935e9..8867ee4 100644 --- a/examples/gradle.properties +++ b/examples/gradle.properties @@ -9,4 +9,7 @@ android.enableJetifier=false # Allow Gradle to use up to 1 GB of RAM org.gradle.jvmargs=-Xmx1024M -android.nonTransitiveRClass=false \ No newline at end of file +android.nonTransitiveRClass=false + +org.jetbrains.dokka.experimental.gradle.pluginMode=V2Enabled +org.jetbrains.dokka.experimental.gradle.pluginMode.noWarn=true \ No newline at end of file diff --git a/examples/settings.gradle.kts b/examples/settings.gradle.kts index 3ef085d..903bc08 100644 --- a/examples/settings.gradle.kts +++ b/examples/settings.gradle.kts @@ -2,6 +2,7 @@ pluginManagement { includeBuild("../library/plugin-svelte-assets") repositories { gradlePluginPortal() + maven("https://repo.dairy.foundation/releases") } } From 4e1ec049dba2fb161b6c4cb9fe9a5f36e91e23c9 Mon Sep 17 00:00:00 2001 From: Oscar Chevalier Date: Sun, 11 Jan 2026 06:29:34 +1100 Subject: [PATCH 09/15] chore: `chmod +x gradlew` --- examples/gradlew | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 examples/gradlew diff --git a/examples/gradlew b/examples/gradlew old mode 100644 new mode 100755 From c0b68379dabb95baac54d28a1e8f8177d94a6bfd Mon Sep 17 00:00:00 2001 From: Oscar Chevalier Date: Sun, 11 Jan 2026 06:29:57 +1100 Subject: [PATCH 10/15] chore: add sloth to TeamCode --- library/TeamCode/build.gradle | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/library/TeamCode/build.gradle b/library/TeamCode/build.gradle index 9ac8b4c..c9de007 100644 --- a/library/TeamCode/build.gradle +++ b/library/TeamCode/build.gradle @@ -12,9 +12,22 @@ // Custom definitions may go here // Include common definitions from above. +buildscript { + repositories { + mavenCentral() + maven { + url "https://repo.dairy.foundation/releases" + } + } + dependencies { + classpath "dev.frozenmilk:Load:0.2.4" + } +} + apply from: '../build.common.gradle' apply from: '../build.dependencies.gradle' apply plugin: 'org.jetbrains.kotlin.android' +apply plugin: 'dev.frozenmilk.sinister.sloth.load' android { namespace = 'org.firstinspires.ftc.teamcode' @@ -44,4 +57,5 @@ dependencies { implementation project(':FtcRobotController') implementation project(':FullPanels') implementation project(':ExamplePlugin') + implementation("dev.frozenmilk.sinister:Sloth:0.2.4") } From 80bfc48405fdc757f9e433af06145348562f8cea Mon Sep 17 00:00:00 2001 From: Oscar Chevalier Date: Sun, 11 Jan 2026 06:30:23 +1100 Subject: [PATCH 11/15] fix: add back support for refreshClass --- .../configurables/ConfigurablesPlugin.kt | 18 ++++++++++++++---- .../configurables/PanelsConfigurables.kt | 16 ++++++++++++++++ 2 files changed, 30 insertions(+), 4 deletions(-) create mode 100644 library/Configurables/src/main/java/com/bylazar/configurables/PanelsConfigurables.kt diff --git a/library/Configurables/src/main/java/com/bylazar/configurables/ConfigurablesPlugin.kt b/library/Configurables/src/main/java/com/bylazar/configurables/ConfigurablesPlugin.kt index 8004084..cfe8d05 100644 --- a/library/Configurables/src/main/java/com/bylazar/configurables/ConfigurablesPlugin.kt +++ b/library/Configurables/src/main/java/com/bylazar/configurables/ConfigurablesPlugin.kt @@ -59,6 +59,16 @@ object Plugin : Plugin(ConfigurablesPluginConfig()), } } + fun refreshClass(cls: Class<*>) { + jvmFields.forEach { (loader, fields) -> + if(fields.removeIf { it.reference.declaringClass == cls }) { + scan(loader, cls) + send("configurables", allFieldsMap) + return + } + } + } + override fun onRegister( panelsInstance: Panels, context: Context ) { @@ -131,16 +141,16 @@ object Plugin : Plugin(ConfigurablesPluginConfig()), } override fun afterScan(loader: ClassLoader) { - if (loader is RootClassLoader) { - initialFieldsMap = allFieldsMap - send("initialConfigurables", initialFieldsMap) - } + initialFieldsMap = allFieldsMap + send("initialConfigurables", initialFieldsMap) send("configurables", allFieldsMap) } override fun beforeUnload(loader: ClassLoader) { jvmFields.remove(loader) loadedFieldsMap.remove(loader)?.forEach(fieldsMap::remove) + initialFieldsMap = allFieldsMap + send("initialConfigurables", initialFieldsMap) send("configurables", allFieldsMap) } diff --git a/library/Configurables/src/main/java/com/bylazar/configurables/PanelsConfigurables.kt b/library/Configurables/src/main/java/com/bylazar/configurables/PanelsConfigurables.kt new file mode 100644 index 0000000..d9a6754 --- /dev/null +++ b/library/Configurables/src/main/java/com/bylazar/configurables/PanelsConfigurables.kt @@ -0,0 +1,16 @@ +package com.bylazar.configurables + +import kotlin.reflect.KClass + +object PanelsConfigurables { + @Deprecated( + "this function is misleading, instead, use the variants that take a class", + replaceWith = ReplaceWith("refreshClass(cls::class)") + ) + @JvmStatic + fun refreshClass(cls: Any) = refreshClass(cls.javaClass) + @JvmStatic + fun refreshClass(cls: KClass<*>) = Plugin.refreshClass(cls.java) + @JvmStatic + fun refreshClass(cls: Class<*>) = Plugin.refreshClass(cls) +} \ No newline at end of file From 1b63a5a70993e2c2f70721ba71bb86cbd8f1bb78 Mon Sep 17 00:00:00 2001 From: Oscar Chevalier Date: Sun, 11 Jan 2026 06:31:29 +1100 Subject: [PATCH 12/15] fix: improve logger behaviour, and fix some usages --- .../main/java/com/bylazar/panels/Logger.kt | 21 ++++++++++++++----- .../main/java/com/bylazar/panels/Panels.kt | 2 +- .../java/com/bylazar/panels/plugins/Plugin.kt | 8 +++++-- .../com/bylazar/panels/server/StaticServer.kt | 9 ++++---- 4 files changed, 28 insertions(+), 12 deletions(-) diff --git a/library/Panels/src/main/java/com/bylazar/panels/Logger.kt b/library/Panels/src/main/java/com/bylazar/panels/Logger.kt index 64fe467..58555df 100644 --- a/library/Panels/src/main/java/com/bylazar/panels/Logger.kt +++ b/library/Panels/src/main/java/com/bylazar/panels/Logger.kt @@ -1,5 +1,7 @@ package com.bylazar.panels +import com.qualcomm.robotcore.util.RobotLog + object Logger { const val REFLECTION_PREFIX = "Reflection" const val SOCKET_PREFIX = "Socket" @@ -7,8 +9,8 @@ object Logger { const val PLUGINS_PREFIX = "Plugins" const val CORE_PREFIX = "Core" - private fun getCallerClassName(): String { - if(!Panels.config.enableClassCallerLogs) return "Disabled" + private fun getCallerClassName(): String? { + if(!Panels.config.enableClassCallerLogs) return null return Throwable() .stackTrace .firstOrNull { !it.className.contains("com.bylazar.panels.Logger") } @@ -17,14 +19,20 @@ object Logger { ?: "Unknown" } + private fun logCallerClass(message: String) = + getCallerClassName()?.let { "($it): $message" } ?: message + fun log(prefix: String, message: String) { if(!Panels.config.enableLogs) return - println("PANELS: ${prefix.uppercase()}: (${getCallerClassName()}): $message") + RobotLog.ii("Panels::$prefix", logCallerClass(message)) } fun error(prefix: String, message: String) { - if(!Panels.config.enableLogs) return - println("PANELS: ${prefix.uppercase()}: (${getCallerClassName()}): ERROR: $message") + RobotLog.ee("Panels::$prefix", logCallerClass(message)) + } + + fun error(prefix: String, message: String, e: Throwable) { + RobotLog.ee("Panels@$prefix", e, logCallerClass(message)) } fun reflectionLog(message: String) = log(REFLECTION_PREFIX, message) @@ -32,13 +40,16 @@ object Logger { fun socketLog(message: String) = log(SOCKET_PREFIX, message) fun socketError(message: String) = error(SOCKET_PREFIX, message) + fun socketError(message: String, e: Throwable) = error(SOCKET_PREFIX, message, e) fun serverLog(message: String) = log(SERVER_PREFIX, message) fun serverError(message: String) = error(SERVER_PREFIX, message) + fun serverError(message: String, e: Throwable) = error(SERVER_PREFIX, message, e) fun pluginsLog(message: String) = log(PLUGINS_PREFIX, message) fun pluginsError(message: String) = error(PLUGINS_PREFIX, message) fun coreLog(message: String) = log(CORE_PREFIX, message) fun coreError(message: String) = error(CORE_PREFIX, message) + fun coreError(message: String, e: Throwable) = error(CORE_PREFIX, message, e) } \ No newline at end of file diff --git a/library/Panels/src/main/java/com/bylazar/panels/Panels.kt b/library/Panels/src/main/java/com/bylazar/panels/Panels.kt index 7acf942..7957d71 100644 --- a/library/Panels/src/main/java/com/bylazar/panels/Panels.kt +++ b/library/Panels/src/main/java/com/bylazar/panels/Panels.kt @@ -77,7 +77,7 @@ object Panels : Notifications { server = StaticServer(context, 8001, "web") socket = Socket(8002) } catch (e: Exception) { - Logger.coreLog("Failed to start webserver: " + e.message) + Logger.coreError("Failed to start webserver", e) } if (PreferencesHandler.isEnabled) { diff --git a/library/Panels/src/main/java/com/bylazar/panels/plugins/Plugin.kt b/library/Panels/src/main/java/com/bylazar/panels/plugins/Plugin.kt index 5d81e40..b0a19d7 100644 --- a/library/Panels/src/main/java/com/bylazar/panels/plugins/Plugin.kt +++ b/library/Panels/src/main/java/com/bylazar/panels/plugins/Plugin.kt @@ -36,11 +36,15 @@ abstract class Plugin(baseConfig: T) { } fun log(message: String) { - Logger.log("${Logger.PLUGINS_PREFIX}/${id}", message) + Logger.log("${Logger.PLUGINS_PREFIX}/${id.ifBlank { "UNKNOWN" }}", message) } fun error(message: String) { - Logger.error("${Logger.PLUGINS_PREFIX}/${id}", message) + Logger.error("${Logger.PLUGINS_PREFIX}/${id.ifBlank { "UNKNOWN" }}", message) + } + + fun error(message: String, e: Throwable) { + Logger.error("${Logger.PLUGINS_PREFIX}/${id.ifBlank { "UNKNOWN" }}", message, e) } fun send(type: String, data: Any) { diff --git a/library/Panels/src/main/java/com/bylazar/panels/server/StaticServer.kt b/library/Panels/src/main/java/com/bylazar/panels/server/StaticServer.kt index 657544f..e473747 100644 --- a/library/Panels/src/main/java/com/bylazar/panels/server/StaticServer.kt +++ b/library/Panels/src/main/java/com/bylazar/panels/server/StaticServer.kt @@ -25,6 +25,7 @@ class StaticServer( init { var files = listWebFiles(baseFolder) + Logger.serverLog("Found ${files.size} web files") files.forEach { Logger.serverLog("Found file: $it") } @@ -280,15 +281,15 @@ class StaticServer( Logger.serverLog("Success") return newChunkedResponse(Response.Status.OK, mime, inputStream).allowCors() } catch (e: Exception) { - Logger.serverLog("Primary asset not found: $path — ${e.message}") + Logger.serverError("Primary asset not found: $path", e) return try { val fallbackStream = assets.open("$baseFolder/index.html") Logger.serverLog("Fallback to index.html") newChunkedResponse(Response.Status.OK, "text/html", fallbackStream) } catch (fallbackException: Exception) { - val message = "Fallback also failed: ${fallbackException.message}" - Logger.serverLog(message) + val message = "Fallback also failed, check logcat for more info" + Logger.serverError(message, fallbackException) getResponse(message, status = Response.Status.INTERNAL_ERROR) } } @@ -299,7 +300,7 @@ class StaticServer( start() Logger.serverLog("Server started on port $listeningPort") } catch (e: IOException) { - Logger.serverLog("Failed to start server: ${e.message}") + Logger.serverError("Failed to start server", e) } } From 516adad8cd146d649e7c777c41b8029f34942148 Mon Sep 17 00:00:00 2001 From: Oscar Chevalier Date: Sun, 11 Jan 2026 06:33:03 +1100 Subject: [PATCH 13/15] fix: rm .gradle/config.properties --- examples/.gradle/config.properties | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 examples/.gradle/config.properties diff --git a/examples/.gradle/config.properties b/examples/.gradle/config.properties deleted file mode 100644 index beb657d..0000000 --- a/examples/.gradle/config.properties +++ /dev/null @@ -1,2 +0,0 @@ -#Wed Aug 13 13:17:18 EEST 2025 -java.home=C\:\\Program Files\\Android\\Android Studio\\jbr From fcc07a6f63c0c8c6f5dc9d7d9185519e34b44552 Mon Sep 17 00:00:00 2001 From: Oscar Chevalier Date: Sun, 11 Jan 2026 06:56:39 +1100 Subject: [PATCH 14/15] chore: add publish all task --- library/build.gradle.kts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/library/build.gradle.kts b/library/build.gradle.kts index 44ecb6e..f26b430 100644 --- a/library/build.gradle.kts +++ b/library/build.gradle.kts @@ -31,3 +31,19 @@ allprojects { repositories { mavenCentral() } + + +fun makePublishAllTask(repository: String) = tasks.register("publishAllReleasePublicationsTo$repository") { + group = "Publishing" + description = "publish all release publications except ExamplePlugin to $repository" + subprojects.forEach { project -> + if (project.name == "ExamplePlugin") return@forEach + if (project.name == "TeamCode") return@forEach + if (project.name == "FtcRobotController") return@forEach + if (project.name == "plugin-svelte-assets") return@forEach + dependsOn(project.tasks.getByName("publishReleasePublicationTo$repository")) + } +} + +makePublishAllTask("MavenLocal") +makePublishAllTask("DairyRepository") From 9d29a5f928f3133fa9095a7038bdf03bf0d1edaa Mon Sep 17 00:00:00 2001 From: Froze-N-Milk Date: Mon, 2 Feb 2026 13:42:11 +1100 Subject: [PATCH 15/15] fix: switch completely to dairy gradle build --- examples/FtcRobotController/build.gradle | 25 - .../src/main/AndroidManifest.xml | 79 -- .../samples/BasicOmniOpMode_Linear.java | 167 ---- .../samples/BasicOpMode_Iterative.java | 140 --- .../external/samples/BasicOpMode_Linear.java | 115 --- .../external/samples/ConceptAprilTag.java | 217 ----- .../external/samples/ConceptAprilTagEasy.java | 163 ---- .../samples/ConceptAprilTagLocalization.java | 247 ----- .../samples/ConceptAprilTagMultiPortal.java | 104 --- .../ConceptAprilTagOptimizeExposure.java | 246 ----- .../ConceptAprilTagSwitchableCameras.java | 196 ---- .../ConceptExploringIMUOrientation.java | 184 ---- .../samples/ConceptExternalHardwareClass.java | 142 --- .../samples/ConceptGamepadRumble.java | 200 ----- .../samples/ConceptGamepadTouchpad.java | 77 -- .../external/samples/ConceptLEDStick.java | 123 --- .../samples/ConceptMotorBulkRead.java | 227 ----- .../external/samples/ConceptNullOp.java | 89 -- .../samples/ConceptRampMotorSpeed.java | 114 --- .../external/samples/ConceptRevLED.java | 78 -- .../external/samples/ConceptRevSPARKMini.java | 111 --- .../external/samples/ConceptScanServo.java | 115 --- .../external/samples/ConceptSoundsASJava.java | 133 --- .../samples/ConceptSoundsOnBotJava.java | 120 --- .../samples/ConceptSoundsSKYSTONE.java | 122 --- .../external/samples/ConceptTelemetry.java | 177 ---- .../samples/ConceptVisionColorLocator.java | 191 ---- .../samples/ConceptVisionColorSensor.java | 136 --- .../RobotAutoDriveByEncoder_Linear.java | 187 ---- .../samples/RobotAutoDriveByGyro_Linear.java | 429 --------- .../samples/RobotAutoDriveByTime_Linear.java | 128 --- .../samples/RobotAutoDriveToAprilTagOmni.java | 321 ------- .../samples/RobotAutoDriveToAprilTagTank.java | 298 ------ .../samples/RobotAutoDriveToLine_Linear.java | 142 --- .../external/samples/RobotHardware.java | 167 ---- .../samples/RobotTeleopPOV_Linear.java | 159 ---- .../samples/RobotTeleopTank_Iterative.java | 160 ---- .../samples/SampleRevBlinkinLedDriver.java | 163 ---- .../external/samples/SensorBNO055IMU.java | 186 ---- .../samples/SensorBNO055IMUCalibration.java | 230 ----- .../external/samples/SensorColor.java | 221 ----- .../external/samples/SensorDigitalTouch.java | 78 -- .../external/samples/SensorHuskyLens.java | 160 ---- .../samples/SensorIMUNonOrthogonal.java | 184 ---- .../external/samples/SensorIMUOrthogonal.java | 146 --- .../external/samples/SensorKLNavxMicro.java | 128 --- .../external/samples/SensorLimelight3A.java | 159 ---- .../external/samples/SensorMRColor.java | 138 --- .../external/samples/SensorMRGyro.java | 160 ---- .../samples/SensorMROpticalDistance.java | 70 -- .../external/samples/SensorMRRangeSensor.java | 70 -- .../external/samples/SensorOctoQuad.java | 141 --- .../external/samples/SensorOctoQuadAdv.java | 277 ------ .../external/samples/SensorREV2mDistance.java | 87 -- .../external/samples/SensorSparkFunOTOS.java | 156 ---- .../external/samples/SensorTouch.java | 78 -- .../samples/UtilityCameraFrameCapture.java | 127 --- .../samples/UtilityOctoQuadConfigMenu.java | 812 ----------------- .../external/samples/readme.md | 45 - .../external/samples/sample_conventions.md | 113 --- .../internal/FtcOpModeRegister.java | 68 -- .../internal/FtcRobotControllerActivity.java | 845 ------------------ .../internal/PermissionValidatorWrapper.java | 91 -- .../src/main/res/drawable-xhdpi/icon_menu.png | Bin 975 -> 0 bytes .../drawable-xhdpi/icon_robotcontroller.png | Bin 4777 -> 0 bytes .../res/layout/activity_ftc_controller.xml | 184 ---- .../main/res/menu/ftc_robot_controller.xml | 78 -- .../src/main/res/raw/gold.wav | Bin 104016 -> 0 bytes .../src/main/res/raw/silver.wav | Bin 99068 -> 0 bytes .../src/main/res/values/dimens.xml | 40 - .../src/main/res/values/strings.xml | 72 -- .../src/main/res/values/styles.xml | 23 - .../src/main/res/xml/app_settings.xml | 93 -- .../src/main/res/xml/device_filter.xml | 49 - examples/TeamCode/build.gradle | 50 -- .../lib/OpModeAnnotationProcessor.jar | Bin 6032 -> 0 bytes examples/build.common.gradle | 117 --- examples/build.dependencies.gradle | 17 - examples/build.gradle.kts | 36 +- examples/settings.gradle.kts | 19 +- .../src/main/AndroidManifest.xml | 0 .../firstinspires/ftc/teamcode/ClawConfig.kt | 0 .../org/firstinspires/ftc/teamcode/Config.kt | 0 .../org/firstinspires/ftc/teamcode/Drawing.kt | 0 .../ftc/teamcode/TestConfigurables.kt | 0 .../ftc/teamcode/TestConfigurablesValues.kt | 0 .../ftc/teamcode/TestKotlinObject.kt | 0 .../ftc/teamcode/TestTelemetry.kt | 0 .../ftc/teamcode/battery/BatteryExample.kt | 0 .../teamcode/camerastream/TestCameraStream.kt | 0 .../ftc/teamcode/configurables/ClawConfig.kt | 0 .../configurables/JavaRobotConstants.java | 0 .../configurables/KotlinRobotConstants.kt | 0 .../configurables/SortedConfigurables.java | 0 .../teamcode/configurables/TestJavaClass.java | 0 .../teamcode/configurables/TestKotlinClass.kt | 0 .../configurables/TestKotlinObject.kt | 0 .../ftc/teamcode/configurables/TestOpMode.kt | 0 .../copysemantics/bad/Example.kt | 0 .../copysemantics/fixed/Example.kt | 0 .../copysemantics/lambda/Example.kt | 0 .../ftc/teamcode/field/BezierFollower.kt | 0 .../ftc/teamcode/field/CustomPresets.kt | 0 .../ftc/teamcode/field/Drawing.kt | 0 .../ftc/teamcode/field/TestCoords.kt | 0 .../ftc/teamcode/gamepad/TestGamepad.kt | 0 .../ftc/teamcode/graph/TestGraph.kt | 0 .../ftc/teamcode/lights/TestLights.kt | 0 .../ftc/teamcode/opmodecontrol/TimerTest.kt | 0 .../org/firstinspires/ftc/teamcode/readme.md | 0 .../teamcode/telemetry/TestFormatTelemetry.kt | 0 .../teamcode/telemetry/TestJoinedTelemetry.kt | 0 .../telemetry/TestTelemetryFTCStyle.kt | 0 .../telemetry/TestTelemetryPanelsStyle.kt | 0 .../ftc/teamcode/utils/LoopTimerExample.kt | 0 .../teamcode/utils/MovingAverageExample.kt | 0 .../{TeamCode => }/src/main/res/raw/readme.md | 0 .../src/main/res/values/strings.xml | 0 .../main/res/xml/teamwebcamcalibrations.xml | 0 library/Battery/build.gradle.kts | 46 +- library/Battery/web/bun.lock | 1 + library/CameraStream/build.gradle.kts | 45 +- library/CameraStream/web/bun.lock | 1 + library/Capture/build.gradle.kts | 44 +- library/Capture/web/bun.lock | 1 + library/Configurables/build.gradle.kts | 52 +- library/Configurables/web/bun.lock | 1 + library/Docs/build.gradle.kts | 45 +- library/Docs/web/bun.lock | 1 + library/ExamplePlugin/build.gradle.kts | 45 +- library/ExamplePlugin/web/bun.lock | 1 + library/Field/build.gradle.kts | 45 +- library/Field/web/bun.lock | 1 + library/FtcRobotController/build.gradle | 25 - .../src/main/AndroidManifest.xml | 79 -- .../samples/BasicOmniOpMode_Linear.java | 167 ---- .../samples/BasicOpMode_Iterative.java | 140 --- .../external/samples/BasicOpMode_Linear.java | 115 --- .../external/samples/ConceptAprilTag.java | 217 ----- .../external/samples/ConceptAprilTagEasy.java | 163 ---- .../samples/ConceptAprilTagLocalization.java | 247 ----- .../samples/ConceptAprilTagMultiPortal.java | 104 --- .../ConceptAprilTagOptimizeExposure.java | 246 ----- .../ConceptAprilTagSwitchableCameras.java | 196 ---- .../ConceptExploringIMUOrientation.java | 184 ---- .../samples/ConceptExternalHardwareClass.java | 142 --- .../samples/ConceptGamepadRumble.java | 200 ----- .../samples/ConceptGamepadTouchpad.java | 77 -- .../external/samples/ConceptLEDStick.java | 123 --- .../samples/ConceptMotorBulkRead.java | 227 ----- .../external/samples/ConceptNullOp.java | 89 -- .../samples/ConceptRampMotorSpeed.java | 114 --- .../external/samples/ConceptRevLED.java | 78 -- .../external/samples/ConceptRevSPARKMini.java | 111 --- .../external/samples/ConceptScanServo.java | 115 --- .../external/samples/ConceptSoundsASJava.java | 133 --- .../samples/ConceptSoundsOnBotJava.java | 120 --- .../samples/ConceptSoundsSKYSTONE.java | 122 --- .../external/samples/ConceptTelemetry.java | 177 ---- .../samples/ConceptVisionColorLocator.java | 191 ---- .../samples/ConceptVisionColorSensor.java | 136 --- .../RobotAutoDriveByEncoder_Linear.java | 187 ---- .../samples/RobotAutoDriveByGyro_Linear.java | 429 --------- .../samples/RobotAutoDriveByTime_Linear.java | 128 --- .../samples/RobotAutoDriveToAprilTagOmni.java | 321 ------- .../samples/RobotAutoDriveToAprilTagTank.java | 298 ------ .../samples/RobotAutoDriveToLine_Linear.java | 142 --- .../external/samples/RobotHardware.java | 167 ---- .../samples/RobotTeleopPOV_Linear.java | 159 ---- .../samples/RobotTeleopTank_Iterative.java | 160 ---- .../samples/SampleRevBlinkinLedDriver.java | 163 ---- .../external/samples/SensorBNO055IMU.java | 186 ---- .../samples/SensorBNO055IMUCalibration.java | 230 ----- .../external/samples/SensorColor.java | 221 ----- .../external/samples/SensorDigitalTouch.java | 78 -- .../external/samples/SensorHuskyLens.java | 160 ---- .../samples/SensorIMUNonOrthogonal.java | 184 ---- .../external/samples/SensorIMUOrthogonal.java | 146 --- .../external/samples/SensorKLNavxMicro.java | 128 --- .../external/samples/SensorLimelight3A.java | 159 ---- .../external/samples/SensorMRColor.java | 138 --- .../external/samples/SensorMRGyro.java | 160 ---- .../samples/SensorMROpticalDistance.java | 70 -- .../external/samples/SensorMRRangeSensor.java | 70 -- .../external/samples/SensorOctoQuad.java | 137 --- .../external/samples/SensorOctoQuadAdv.java | 277 ------ .../external/samples/SensorREV2mDistance.java | 87 -- .../external/samples/SensorSparkFunOTOS.java | 156 ---- .../external/samples/SensorTouch.java | 78 -- .../samples/UtilityCameraFrameCapture.java | 127 --- .../samples/UtilityOctoQuadConfigMenu.java | 812 ----------------- .../external/samples/readme.md | 45 - .../external/samples/sample_conventions.md | 113 --- .../internal/FtcOpModeRegister.java | 68 -- .../internal/FtcRobotControllerActivity.java | 845 ------------------ .../internal/PermissionValidatorWrapper.java | 91 -- .../src/main/res/drawable-xhdpi/icon_menu.png | Bin 975 -> 0 bytes .../drawable-xhdpi/icon_robotcontroller.png | Bin 4777 -> 0 bytes .../res/layout/activity_ftc_controller.xml | 184 ---- .../main/res/menu/ftc_robot_controller.xml | 78 -- .../src/main/res/raw/gold.wav | Bin 104016 -> 0 bytes .../src/main/res/raw/silver.wav | Bin 99068 -> 0 bytes .../src/main/res/values/dimens.xml | 40 - .../src/main/res/values/strings.xml | 72 -- .../src/main/res/values/styles.xml | 23 - .../src/main/res/xml/app_settings.xml | 93 -- .../src/main/res/xml/device_filter.xml | 49 - library/FullPanels/build.gradle.kts | 49 +- library/FullPanels/web/bun.lock | 1 + library/Gamepad/build.gradle.kts | 44 +- library/Gamepad/web/bun.lock | 1 + library/Graph/build.gradle.kts | 36 +- library/Graph/web/bun.lock | 1 + library/Lights/build.gradle.kts | 45 +- library/Lights/web/bun.lock | 1 + library/LimelightProxy/build.gradle.kts | 46 +- library/LimelightProxy/web/bun.lock | 1 + library/OpModeControl/build.gradle.kts | 52 +- library/OpModeControl/web/bun.lock | 1 + library/Panels/build.gradle.kts | 62 +- .../com/bylazar/panels/TestCompression.kt | 10 +- library/Panels/web/bun.lock | 1 + library/Pinger/build.gradle.kts | 45 +- .../java/com/bylazar/pinger/PingerPlugin.kt | 1 - library/Pinger/web/bun.lock | 1 + library/TeamCode/build.gradle | 61 -- library/TeamCode/build.gradle.kts | 12 + .../lib/OpModeAnnotationProcessor.jar | Bin 6032 -> 0 bytes library/TeamCode/libs/ftc.debug.keystore | Bin 0 -> 2146 bytes library/Telemetry/build.gradle.kts | 45 +- library/Telemetry/web/bun.lock | 1 + library/Themes/build.gradle.kts | 45 +- library/Themes/web/bun.lock | 1 + library/Utils/build.gradle.kts | 47 +- library/Utils/web/bun.lock | 1 + library/build.gradle.kts | 35 - library/settings.gradle.kts | 3 +- 237 files changed, 225 insertions(+), 23990 deletions(-) delete mode 100644 examples/FtcRobotController/build.gradle delete mode 100644 examples/FtcRobotController/src/main/AndroidManifest.xml delete mode 100644 examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/BasicOmniOpMode_Linear.java delete mode 100644 examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/BasicOpMode_Iterative.java delete mode 100644 examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/BasicOpMode_Linear.java delete mode 100644 examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptAprilTag.java delete mode 100644 examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptAprilTagEasy.java delete mode 100644 examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptAprilTagLocalization.java delete mode 100644 examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptAprilTagMultiPortal.java delete mode 100644 examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptAprilTagOptimizeExposure.java delete mode 100644 examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptAprilTagSwitchableCameras.java delete mode 100644 examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptExploringIMUOrientation.java delete mode 100644 examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptExternalHardwareClass.java delete mode 100644 examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptGamepadRumble.java delete mode 100644 examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptGamepadTouchpad.java delete mode 100644 examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptLEDStick.java delete mode 100644 examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptMotorBulkRead.java delete mode 100644 examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptNullOp.java delete mode 100644 examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptRampMotorSpeed.java delete mode 100644 examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptRevLED.java delete mode 100644 examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptRevSPARKMini.java delete mode 100644 examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptScanServo.java delete mode 100644 examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptSoundsASJava.java delete mode 100644 examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptSoundsOnBotJava.java delete mode 100644 examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptSoundsSKYSTONE.java delete mode 100644 examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptTelemetry.java delete mode 100644 examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptVisionColorLocator.java delete mode 100644 examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptVisionColorSensor.java delete mode 100644 examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/RobotAutoDriveByEncoder_Linear.java delete mode 100644 examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/RobotAutoDriveByGyro_Linear.java delete mode 100644 examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/RobotAutoDriveByTime_Linear.java delete mode 100644 examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/RobotAutoDriveToAprilTagOmni.java delete mode 100644 examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/RobotAutoDriveToAprilTagTank.java delete mode 100644 examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/RobotAutoDriveToLine_Linear.java delete mode 100644 examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/RobotHardware.java delete mode 100644 examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/RobotTeleopPOV_Linear.java delete mode 100644 examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/RobotTeleopTank_Iterative.java delete mode 100644 examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SampleRevBlinkinLedDriver.java delete mode 100644 examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorBNO055IMU.java delete mode 100644 examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorBNO055IMUCalibration.java delete mode 100644 examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorColor.java delete mode 100644 examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorDigitalTouch.java delete mode 100644 examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorHuskyLens.java delete mode 100644 examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorIMUNonOrthogonal.java delete mode 100644 examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorIMUOrthogonal.java delete mode 100644 examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorKLNavxMicro.java delete mode 100644 examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorLimelight3A.java delete mode 100644 examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorMRColor.java delete mode 100644 examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorMRGyro.java delete mode 100644 examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorMROpticalDistance.java delete mode 100644 examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorMRRangeSensor.java delete mode 100644 examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorOctoQuad.java delete mode 100644 examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorOctoQuadAdv.java delete mode 100644 examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorREV2mDistance.java delete mode 100644 examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorSparkFunOTOS.java delete mode 100644 examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorTouch.java delete mode 100644 examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/UtilityCameraFrameCapture.java delete mode 100644 examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/UtilityOctoQuadConfigMenu.java delete mode 100644 examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/readme.md delete mode 100644 examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/sample_conventions.md delete mode 100644 examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/internal/FtcOpModeRegister.java delete mode 100644 examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/internal/FtcRobotControllerActivity.java delete mode 100644 examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/internal/PermissionValidatorWrapper.java delete mode 100644 examples/FtcRobotController/src/main/res/drawable-xhdpi/icon_menu.png delete mode 100644 examples/FtcRobotController/src/main/res/drawable-xhdpi/icon_robotcontroller.png delete mode 100644 examples/FtcRobotController/src/main/res/layout/activity_ftc_controller.xml delete mode 100644 examples/FtcRobotController/src/main/res/menu/ftc_robot_controller.xml delete mode 100644 examples/FtcRobotController/src/main/res/raw/gold.wav delete mode 100644 examples/FtcRobotController/src/main/res/raw/silver.wav delete mode 100644 examples/FtcRobotController/src/main/res/values/dimens.xml delete mode 100644 examples/FtcRobotController/src/main/res/values/strings.xml delete mode 100644 examples/FtcRobotController/src/main/res/values/styles.xml delete mode 100644 examples/FtcRobotController/src/main/res/xml/app_settings.xml delete mode 100644 examples/FtcRobotController/src/main/res/xml/device_filter.xml delete mode 100644 examples/TeamCode/build.gradle delete mode 100644 examples/TeamCode/lib/OpModeAnnotationProcessor.jar delete mode 100644 examples/build.common.gradle delete mode 100644 examples/build.dependencies.gradle rename examples/{TeamCode => }/src/main/AndroidManifest.xml (100%) rename examples/{TeamCode => }/src/main/java/org/firstinspires/ftc/teamcode/ClawConfig.kt (100%) rename examples/{TeamCode => }/src/main/java/org/firstinspires/ftc/teamcode/Config.kt (100%) rename examples/{TeamCode => }/src/main/java/org/firstinspires/ftc/teamcode/Drawing.kt (100%) rename examples/{TeamCode => }/src/main/java/org/firstinspires/ftc/teamcode/TestConfigurables.kt (100%) rename examples/{TeamCode => }/src/main/java/org/firstinspires/ftc/teamcode/TestConfigurablesValues.kt (100%) rename examples/{TeamCode => }/src/main/java/org/firstinspires/ftc/teamcode/TestKotlinObject.kt (100%) rename examples/{TeamCode => }/src/main/java/org/firstinspires/ftc/teamcode/TestTelemetry.kt (100%) rename examples/{TeamCode => }/src/main/java/org/firstinspires/ftc/teamcode/battery/BatteryExample.kt (100%) rename examples/{TeamCode => }/src/main/java/org/firstinspires/ftc/teamcode/camerastream/TestCameraStream.kt (100%) rename examples/{TeamCode => }/src/main/java/org/firstinspires/ftc/teamcode/configurables/ClawConfig.kt (100%) rename examples/{TeamCode => }/src/main/java/org/firstinspires/ftc/teamcode/configurables/JavaRobotConstants.java (100%) rename examples/{TeamCode => }/src/main/java/org/firstinspires/ftc/teamcode/configurables/KotlinRobotConstants.kt (100%) rename examples/{TeamCode => }/src/main/java/org/firstinspires/ftc/teamcode/configurables/SortedConfigurables.java (100%) rename examples/{TeamCode => }/src/main/java/org/firstinspires/ftc/teamcode/configurables/TestJavaClass.java (100%) rename examples/{TeamCode => }/src/main/java/org/firstinspires/ftc/teamcode/configurables/TestKotlinClass.kt (100%) rename examples/{TeamCode => }/src/main/java/org/firstinspires/ftc/teamcode/configurables/TestKotlinObject.kt (100%) rename examples/{TeamCode => }/src/main/java/org/firstinspires/ftc/teamcode/configurables/TestOpMode.kt (100%) rename examples/{TeamCode => }/src/main/java/org/firstinspires/ftc/teamcode/configurables/copysemantics/bad/Example.kt (100%) rename examples/{TeamCode => }/src/main/java/org/firstinspires/ftc/teamcode/configurables/copysemantics/fixed/Example.kt (100%) rename examples/{TeamCode => }/src/main/java/org/firstinspires/ftc/teamcode/configurables/copysemantics/lambda/Example.kt (100%) rename examples/{TeamCode => }/src/main/java/org/firstinspires/ftc/teamcode/field/BezierFollower.kt (100%) rename examples/{TeamCode => }/src/main/java/org/firstinspires/ftc/teamcode/field/CustomPresets.kt (100%) rename examples/{TeamCode => }/src/main/java/org/firstinspires/ftc/teamcode/field/Drawing.kt (100%) rename examples/{TeamCode => }/src/main/java/org/firstinspires/ftc/teamcode/field/TestCoords.kt (100%) rename examples/{TeamCode => }/src/main/java/org/firstinspires/ftc/teamcode/gamepad/TestGamepad.kt (100%) rename examples/{TeamCode => }/src/main/java/org/firstinspires/ftc/teamcode/graph/TestGraph.kt (100%) rename examples/{TeamCode => }/src/main/java/org/firstinspires/ftc/teamcode/lights/TestLights.kt (100%) rename examples/{TeamCode => }/src/main/java/org/firstinspires/ftc/teamcode/opmodecontrol/TimerTest.kt (100%) rename examples/{TeamCode => }/src/main/java/org/firstinspires/ftc/teamcode/readme.md (100%) rename examples/{TeamCode => }/src/main/java/org/firstinspires/ftc/teamcode/telemetry/TestFormatTelemetry.kt (100%) rename examples/{TeamCode => }/src/main/java/org/firstinspires/ftc/teamcode/telemetry/TestJoinedTelemetry.kt (100%) rename examples/{TeamCode => }/src/main/java/org/firstinspires/ftc/teamcode/telemetry/TestTelemetryFTCStyle.kt (100%) rename examples/{TeamCode => }/src/main/java/org/firstinspires/ftc/teamcode/telemetry/TestTelemetryPanelsStyle.kt (100%) rename examples/{TeamCode => }/src/main/java/org/firstinspires/ftc/teamcode/utils/LoopTimerExample.kt (100%) rename examples/{TeamCode => }/src/main/java/org/firstinspires/ftc/teamcode/utils/MovingAverageExample.kt (100%) rename examples/{TeamCode => }/src/main/res/raw/readme.md (100%) rename examples/{TeamCode => }/src/main/res/values/strings.xml (100%) rename examples/{TeamCode => }/src/main/res/xml/teamwebcamcalibrations.xml (100%) delete mode 100644 library/FtcRobotController/build.gradle delete mode 100644 library/FtcRobotController/src/main/AndroidManifest.xml delete mode 100644 library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/BasicOmniOpMode_Linear.java delete mode 100644 library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/BasicOpMode_Iterative.java delete mode 100644 library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/BasicOpMode_Linear.java delete mode 100644 library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptAprilTag.java delete mode 100644 library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptAprilTagEasy.java delete mode 100644 library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptAprilTagLocalization.java delete mode 100644 library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptAprilTagMultiPortal.java delete mode 100644 library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptAprilTagOptimizeExposure.java delete mode 100644 library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptAprilTagSwitchableCameras.java delete mode 100644 library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptExploringIMUOrientation.java delete mode 100644 library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptExternalHardwareClass.java delete mode 100644 library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptGamepadRumble.java delete mode 100644 library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptGamepadTouchpad.java delete mode 100644 library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptLEDStick.java delete mode 100644 library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptMotorBulkRead.java delete mode 100644 library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptNullOp.java delete mode 100644 library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptRampMotorSpeed.java delete mode 100644 library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptRevLED.java delete mode 100644 library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptRevSPARKMini.java delete mode 100644 library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptScanServo.java delete mode 100644 library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptSoundsASJava.java delete mode 100644 library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptSoundsOnBotJava.java delete mode 100644 library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptSoundsSKYSTONE.java delete mode 100644 library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptTelemetry.java delete mode 100644 library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptVisionColorLocator.java delete mode 100644 library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptVisionColorSensor.java delete mode 100644 library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/RobotAutoDriveByEncoder_Linear.java delete mode 100644 library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/RobotAutoDriveByGyro_Linear.java delete mode 100644 library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/RobotAutoDriveByTime_Linear.java delete mode 100644 library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/RobotAutoDriveToAprilTagOmni.java delete mode 100644 library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/RobotAutoDriveToAprilTagTank.java delete mode 100644 library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/RobotAutoDriveToLine_Linear.java delete mode 100644 library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/RobotHardware.java delete mode 100644 library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/RobotTeleopPOV_Linear.java delete mode 100644 library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/RobotTeleopTank_Iterative.java delete mode 100644 library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SampleRevBlinkinLedDriver.java delete mode 100644 library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorBNO055IMU.java delete mode 100644 library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorBNO055IMUCalibration.java delete mode 100644 library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorColor.java delete mode 100644 library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorDigitalTouch.java delete mode 100644 library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorHuskyLens.java delete mode 100644 library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorIMUNonOrthogonal.java delete mode 100644 library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorIMUOrthogonal.java delete mode 100644 library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorKLNavxMicro.java delete mode 100644 library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorLimelight3A.java delete mode 100644 library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorMRColor.java delete mode 100644 library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorMRGyro.java delete mode 100644 library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorMROpticalDistance.java delete mode 100644 library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorMRRangeSensor.java delete mode 100644 library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorOctoQuad.java delete mode 100644 library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorOctoQuadAdv.java delete mode 100644 library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorREV2mDistance.java delete mode 100644 library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorSparkFunOTOS.java delete mode 100644 library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorTouch.java delete mode 100644 library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/UtilityCameraFrameCapture.java delete mode 100644 library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/UtilityOctoQuadConfigMenu.java delete mode 100644 library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/readme.md delete mode 100644 library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/sample_conventions.md delete mode 100644 library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/internal/FtcOpModeRegister.java delete mode 100644 library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/internal/FtcRobotControllerActivity.java delete mode 100644 library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/internal/PermissionValidatorWrapper.java delete mode 100644 library/FtcRobotController/src/main/res/drawable-xhdpi/icon_menu.png delete mode 100644 library/FtcRobotController/src/main/res/drawable-xhdpi/icon_robotcontroller.png delete mode 100644 library/FtcRobotController/src/main/res/layout/activity_ftc_controller.xml delete mode 100644 library/FtcRobotController/src/main/res/menu/ftc_robot_controller.xml delete mode 100644 library/FtcRobotController/src/main/res/raw/gold.wav delete mode 100644 library/FtcRobotController/src/main/res/raw/silver.wav delete mode 100644 library/FtcRobotController/src/main/res/values/dimens.xml delete mode 100644 library/FtcRobotController/src/main/res/values/strings.xml delete mode 100644 library/FtcRobotController/src/main/res/values/styles.xml delete mode 100644 library/FtcRobotController/src/main/res/xml/app_settings.xml delete mode 100644 library/FtcRobotController/src/main/res/xml/device_filter.xml delete mode 100644 library/TeamCode/build.gradle create mode 100644 library/TeamCode/build.gradle.kts delete mode 100644 library/TeamCode/lib/OpModeAnnotationProcessor.jar create mode 100644 library/TeamCode/libs/ftc.debug.keystore diff --git a/examples/FtcRobotController/build.gradle b/examples/FtcRobotController/build.gradle deleted file mode 100644 index 4693793..0000000 --- a/examples/FtcRobotController/build.gradle +++ /dev/null @@ -1,25 +0,0 @@ -import java.text.SimpleDateFormat - -apply plugin: 'com.android.library' - -apply from: '../build.dependencies.gradle' -android { - - defaultConfig { - minSdkVersion 24 - //noinspection ExpiredTargetSdkVersion - targetSdkVersion 28 - compileSdk = 35 - buildConfigField "String", "APP_BUILD_TIME", '"' + (new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ", Locale.ROOT).format(new Date())) + '"' - } - - buildFeatures { - buildConfig = true - } - - compileOptions { - sourceCompatibility JavaVersion.VERSION_11 - targetCompatibility JavaVersion.VERSION_11 - } - namespace = 'com.qualcomm.ftcrobotcontroller' -} diff --git a/examples/FtcRobotController/src/main/AndroidManifest.xml b/examples/FtcRobotController/src/main/AndroidManifest.xml deleted file mode 100644 index 24038bf..0000000 --- a/examples/FtcRobotController/src/main/AndroidManifest.xml +++ /dev/null @@ -1,79 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/BasicOmniOpMode_Linear.java b/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/BasicOmniOpMode_Linear.java deleted file mode 100644 index 1d14dfb..0000000 --- a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/BasicOmniOpMode_Linear.java +++ /dev/null @@ -1,167 +0,0 @@ -/* Copyright (c) 2021 FIRST. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted (subject to the limitations in the disclaimer below) provided that - * the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list - * of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this - * list of conditions and the following disclaimer in the documentation and/or - * other materials provided with the distribution. - * - * Neither the name of FIRST nor the names of its contributors may be used to endorse or - * promote products derived from this software without specific prior written permission. - * - * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS - * LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.firstinspires.ftc.robotcontroller.external.samples; - -import com.qualcomm.robotcore.eventloop.opmode.Disabled; -import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode; -import com.qualcomm.robotcore.eventloop.opmode.TeleOp; -import com.qualcomm.robotcore.hardware.DcMotor; -import com.qualcomm.robotcore.util.ElapsedTime; - -/* - * This file contains an example of a Linear "OpMode". - * An OpMode is a 'program' that runs in either the autonomous or the teleop period of an FTC match. - * The names of OpModes appear on the menu of the FTC Driver Station. - * When a selection is made from the menu, the corresponding OpMode is executed. - * - * This particular OpMode illustrates driving a 4-motor Omni-Directional (or Holonomic) robot. - * This code will work with either a Mecanum-Drive or an X-Drive train. - * Both of these drives are illustrated at https://gm0.org/en/latest/docs/robot-design/drivetrains/holonomic.html - * Note that a Mecanum drive must display an X roller-pattern when viewed from above. - * - * Also note that it is critical to set the correct rotation direction for each motor. See details below. - * - * Holonomic drives provide the ability for the robot to move in three axes (directions) simultaneously. - * Each motion axis is controlled by one Joystick axis. - * - * 1) Axial: Driving forward and backward Left-joystick Forward/Backward - * 2) Lateral: Strafing right and left Left-joystick Right and Left - * 3) Yaw: Rotating Clockwise and counter clockwise Right-joystick Right and Left - * - * This code is written assuming that the right-side motors need to be reversed for the robot to drive forward. - * When you first test your robot, if it moves backward when you push the left stick forward, then you must flip - * the direction of all 4 motors (see code below). - * - * Use Android Studio to Copy this Class, and Paste it into your team's code folder with a new name. - * Remove or comment out the @Disabled line to add this OpMode to the Driver Station OpMode list - */ - -@TeleOp(name="Basic: Omni Linear OpMode", group="Linear OpMode") -@Disabled -public class BasicOmniOpMode_Linear extends LinearOpMode { - - // Declare OpMode members for each of the 4 motors. - private ElapsedTime runtime = new ElapsedTime(); - private DcMotor leftFrontDrive = null; - private DcMotor leftBackDrive = null; - private DcMotor rightFrontDrive = null; - private DcMotor rightBackDrive = null; - - @Override - public void runOpMode() { - - // Initialize the hardware variables. Note that the strings used here must correspond - // to the names assigned during the robot configuration step on the DS or RC devices. - leftFrontDrive = hardwareMap.get(DcMotor.class, "left_front_drive"); - leftBackDrive = hardwareMap.get(DcMotor.class, "left_back_drive"); - rightFrontDrive = hardwareMap.get(DcMotor.class, "right_front_drive"); - rightBackDrive = hardwareMap.get(DcMotor.class, "right_back_drive"); - - // ######################################################################################## - // !!! IMPORTANT Drive Information. Test your motor directions. !!!!! - // ######################################################################################## - // Most robots need the motors on one side to be reversed to drive forward. - // The motor reversals shown here are for a "direct drive" robot (the wheels turn the same direction as the motor shaft) - // If your robot has additional gear reductions or uses a right-angled drive, it's important to ensure - // that your motors are turning in the correct direction. So, start out with the reversals here, BUT - // when you first test your robot, push the left joystick forward and observe the direction the wheels turn. - // Reverse the direction (flip FORWARD <-> REVERSE ) of any wheel that runs backward - // Keep testing until ALL the wheels move the robot forward when you push the left joystick forward. - leftFrontDrive.setDirection(DcMotor.Direction.REVERSE); - leftBackDrive.setDirection(DcMotor.Direction.REVERSE); - rightFrontDrive.setDirection(DcMotor.Direction.FORWARD); - rightBackDrive.setDirection(DcMotor.Direction.FORWARD); - - // Wait for the game to start (driver presses START) - telemetry.addData("Status", "Initialized"); - telemetry.update(); - - waitForStart(); - runtime.reset(); - - // run until the end of the match (driver presses STOP) - while (opModeIsActive()) { - double max; - - // POV Mode uses left joystick to go forward & strafe, and right joystick to rotate. - double axial = -gamepad1.left_stick_y; // Note: pushing stick forward gives negative value - double lateral = gamepad1.left_stick_x; - double yaw = gamepad1.right_stick_x; - - // Combine the joystick requests for each axis-motion to determine each wheel's power. - // Set up a variable for each drive wheel to save the power level for telemetry. - double leftFrontPower = axial + lateral + yaw; - double rightFrontPower = axial - lateral - yaw; - double leftBackPower = axial - lateral + yaw; - double rightBackPower = axial + lateral - yaw; - - // Normalize the values so no wheel power exceeds 100% - // This ensures that the robot maintains the desired motion. - max = Math.max(Math.abs(leftFrontPower), Math.abs(rightFrontPower)); - max = Math.max(max, Math.abs(leftBackPower)); - max = Math.max(max, Math.abs(rightBackPower)); - - if (max > 1.0) { - leftFrontPower /= max; - rightFrontPower /= max; - leftBackPower /= max; - rightBackPower /= max; - } - - // This is test code: - // - // Uncomment the following code to test your motor directions. - // Each button should make the corresponding motor run FORWARD. - // 1) First get all the motors to take to correct positions on the robot - // by adjusting your Robot Configuration if necessary. - // 2) Then make sure they run in the correct direction by modifying the - // the setDirection() calls above. - // Once the correct motors move in the correct direction re-comment this code. - - /* - leftFrontPower = gamepad1.x ? 1.0 : 0.0; // X gamepad - leftBackPower = gamepad1.a ? 1.0 : 0.0; // A gamepad - rightFrontPower = gamepad1.y ? 1.0 : 0.0; // Y gamepad - rightBackPower = gamepad1.b ? 1.0 : 0.0; // B gamepad - */ - - // Send calculated power to wheels - leftFrontDrive.setPower(leftFrontPower); - rightFrontDrive.setPower(rightFrontPower); - leftBackDrive.setPower(leftBackPower); - rightBackDrive.setPower(rightBackPower); - - // Show the elapsed game time and wheel power. - telemetry.addData("Status", "Run Time: " + runtime.toString()); - telemetry.addData("Front left/Right", "%4.2f, %4.2f", leftFrontPower, rightFrontPower); - telemetry.addData("Back left/Right", "%4.2f, %4.2f", leftBackPower, rightBackPower); - telemetry.update(); - } - }} diff --git a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/BasicOpMode_Iterative.java b/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/BasicOpMode_Iterative.java deleted file mode 100644 index 6504e58..0000000 --- a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/BasicOpMode_Iterative.java +++ /dev/null @@ -1,140 +0,0 @@ -/* Copyright (c) 2017 FIRST. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted (subject to the limitations in the disclaimer below) provided that - * the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list - * of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this - * list of conditions and the following disclaimer in the documentation and/or - * other materials provided with the distribution. - * - * Neither the name of FIRST nor the names of its contributors may be used to endorse or - * promote products derived from this software without specific prior written permission. - * - * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS - * LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.firstinspires.ftc.robotcontroller.external.samples; - -import com.qualcomm.robotcore.eventloop.opmode.Disabled; -import com.qualcomm.robotcore.eventloop.opmode.OpMode; -import com.qualcomm.robotcore.eventloop.opmode.TeleOp; -import com.qualcomm.robotcore.hardware.DcMotor; -import com.qualcomm.robotcore.util.ElapsedTime; -import com.qualcomm.robotcore.util.Range; - -/* - * This file contains an example of an iterative (Non-Linear) "OpMode". - * An OpMode is a 'program' that runs in either the autonomous or the teleop period of an FTC match. - * The names of OpModes appear on the menu of the FTC Driver Station. - * When a selection is made from the menu, the corresponding OpMode - * class is instantiated on the Robot Controller and executed. - * - * This particular OpMode just executes a basic Tank Drive Teleop for a two wheeled robot - * It includes all the skeletal structure that all iterative OpModes contain. - * - * Use Android Studio to Copy this Class, and Paste it into your team's code folder with a new name. - * Remove or comment out the @Disabled line to add this OpMode to the Driver Station OpMode list - */ - -@TeleOp(name="Basic: Iterative OpMode", group="Iterative OpMode") -@Disabled -public class BasicOpMode_Iterative extends OpMode -{ - // Declare OpMode members. - private ElapsedTime runtime = new ElapsedTime(); - private DcMotor leftDrive = null; - private DcMotor rightDrive = null; - - /* - * Code to run ONCE when the driver hits INIT - */ - @Override - public void init() { - telemetry.addData("Status", "Initialized"); - - // Initialize the hardware variables. Note that the strings used here as parameters - // to 'get' must correspond to the names assigned during the robot configuration - // step (using the FTC Robot Controller app on the phone). - leftDrive = hardwareMap.get(DcMotor.class, "left_drive"); - rightDrive = hardwareMap.get(DcMotor.class, "right_drive"); - - // To drive forward, most robots need the motor on one side to be reversed, because the axles point in opposite directions. - // Pushing the left stick forward MUST make robot go forward. So adjust these two lines based on your first test drive. - // Note: The settings here assume direct drive on left and right wheels. Gear Reduction or 90 Deg drives may require direction flips - leftDrive.setDirection(DcMotor.Direction.REVERSE); - rightDrive.setDirection(DcMotor.Direction.FORWARD); - - // Tell the driver that initialization is complete. - telemetry.addData("Status", "Initialized"); - } - - /* - * Code to run REPEATEDLY after the driver hits INIT, but before they hit START - */ - @Override - public void init_loop() { - } - - /* - * Code to run ONCE when the driver hits START - */ - @Override - public void start() { - runtime.reset(); - } - - /* - * Code to run REPEATEDLY after the driver hits START but before they hit STOP - */ - @Override - public void loop() { - // Setup a variable for each drive wheel to save power level for telemetry - double leftPower; - double rightPower; - - // Choose to drive using either Tank Mode, or POV Mode - // Comment out the method that's not used. The default below is POV. - - // POV Mode uses left stick to go forward, and right stick to turn. - // - This uses basic math to combine motions and is easier to drive straight. - double drive = -gamepad1.left_stick_y; - double turn = gamepad1.right_stick_x; - leftPower = Range.clip(drive + turn, -1.0, 1.0) ; - rightPower = Range.clip(drive - turn, -1.0, 1.0) ; - - // Tank Mode uses one stick to control each wheel. - // - This requires no math, but it is hard to drive forward slowly and keep straight. - // leftPower = -gamepad1.left_stick_y ; - // rightPower = -gamepad1.right_stick_y ; - - // Send calculated power to wheels - leftDrive.setPower(leftPower); - rightDrive.setPower(rightPower); - - // Show the elapsed game time and wheel power. - telemetry.addData("Status", "Run Time: " + runtime.toString()); - telemetry.addData("Motors", "left (%.2f), right (%.2f)", leftPower, rightPower); - } - - /* - * Code to run ONCE after the driver hits STOP - */ - @Override - public void stop() { - } - -} diff --git a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/BasicOpMode_Linear.java b/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/BasicOpMode_Linear.java deleted file mode 100644 index ab0bb25..0000000 --- a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/BasicOpMode_Linear.java +++ /dev/null @@ -1,115 +0,0 @@ -/* Copyright (c) 2017 FIRST. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted (subject to the limitations in the disclaimer below) provided that - * the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list - * of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this - * list of conditions and the following disclaimer in the documentation and/or - * other materials provided with the distribution. - * - * Neither the name of FIRST nor the names of its contributors may be used to endorse or - * promote products derived from this software without specific prior written permission. - * - * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS - * LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.firstinspires.ftc.robotcontroller.external.samples; - -import com.qualcomm.robotcore.eventloop.opmode.Disabled; -import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode; -import com.qualcomm.robotcore.eventloop.opmode.TeleOp; -import com.qualcomm.robotcore.hardware.DcMotor; -import com.qualcomm.robotcore.util.ElapsedTime; -import com.qualcomm.robotcore.util.Range; - - -/* - * This file contains an minimal example of a Linear "OpMode". An OpMode is a 'program' that runs in either - * the autonomous or the teleop period of an FTC match. The names of OpModes appear on the menu - * of the FTC Driver Station. When a selection is made from the menu, the corresponding OpMode - * class is instantiated on the Robot Controller and executed. - * - * This particular OpMode just executes a basic Tank Drive Teleop for a two wheeled robot - * It includes all the skeletal structure that all linear OpModes contain. - * - * Use Android Studio to Copy this Class, and Paste it into your team's code folder with a new name. - * Remove or comment out the @Disabled line to add this OpMode to the Driver Station OpMode list - */ - -@TeleOp(name="Basic: Linear OpMode", group="Linear OpMode") -@Disabled -public class BasicOpMode_Linear extends LinearOpMode { - - // Declare OpMode members. - private ElapsedTime runtime = new ElapsedTime(); - private DcMotor leftDrive = null; - private DcMotor rightDrive = null; - - @Override - public void runOpMode() { - telemetry.addData("Status", "Initialized"); - telemetry.update(); - - // Initialize the hardware variables. Note that the strings used here as parameters - // to 'get' must correspond to the names assigned during the robot configuration - // step (using the FTC Robot Controller app on the phone). - leftDrive = hardwareMap.get(DcMotor.class, "left_drive"); - rightDrive = hardwareMap.get(DcMotor.class, "right_drive"); - - // To drive forward, most robots need the motor on one side to be reversed, because the axles point in opposite directions. - // Pushing the left stick forward MUST make robot go forward. So adjust these two lines based on your first test drive. - // Note: The settings here assume direct drive on left and right wheels. Gear Reduction or 90 Deg drives may require direction flips - leftDrive.setDirection(DcMotor.Direction.REVERSE); - rightDrive.setDirection(DcMotor.Direction.FORWARD); - - // Wait for the game to start (driver presses START) - waitForStart(); - runtime.reset(); - - // run until the end of the match (driver presses STOP) - while (opModeIsActive()) { - - // Setup a variable for each drive wheel to save power level for telemetry - double leftPower; - double rightPower; - - // Choose to drive using either Tank Mode, or POV Mode - // Comment out the method that's not used. The default below is POV. - - // POV Mode uses left stick to go forward, and right stick to turn. - // - This uses basic math to combine motions and is easier to drive straight. - double drive = -gamepad1.left_stick_y; - double turn = gamepad1.right_stick_x; - leftPower = Range.clip(drive + turn, -1.0, 1.0) ; - rightPower = Range.clip(drive - turn, -1.0, 1.0) ; - - // Tank Mode uses one stick to control each wheel. - // - This requires no math, but it is hard to drive forward slowly and keep straight. - // leftPower = -gamepad1.left_stick_y ; - // rightPower = -gamepad1.right_stick_y ; - - // Send calculated power to wheels - leftDrive.setPower(leftPower); - rightDrive.setPower(rightPower); - - // Show the elapsed game time and wheel power. - telemetry.addData("Status", "Run Time: " + runtime.toString()); - telemetry.addData("Motors", "left (%.2f), right (%.2f)", leftPower, rightPower); - telemetry.update(); - } - } -} diff --git a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptAprilTag.java b/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptAprilTag.java deleted file mode 100644 index 8ec77dd..0000000 --- a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptAprilTag.java +++ /dev/null @@ -1,217 +0,0 @@ -/* Copyright (c) 2023 FIRST. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted (subject to the limitations in the disclaimer below) provided that - * the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list - * of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this - * list of conditions and the following disclaimer in the documentation and/or - * other materials provided with the distribution. - * - * Neither the name of FIRST nor the names of its contributors may be used to endorse or - * promote products derived from this software without specific prior written permission. - * - * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS - * LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.firstinspires.ftc.robotcontroller.external.samples; - -import android.util.Size; -import com.qualcomm.robotcore.eventloop.opmode.Disabled; -import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode; -import com.qualcomm.robotcore.eventloop.opmode.TeleOp; -import org.firstinspires.ftc.robotcore.external.hardware.camera.BuiltinCameraDirection; -import org.firstinspires.ftc.robotcore.external.hardware.camera.WebcamName; -import org.firstinspires.ftc.vision.VisionPortal; -import org.firstinspires.ftc.vision.apriltag.AprilTagDetection; -import org.firstinspires.ftc.vision.apriltag.AprilTagProcessor; - -import java.util.List; - -/* - * This OpMode illustrates the basics of AprilTag recognition and pose estimation, - * including Java Builder structures for specifying Vision parameters. - * - * For an introduction to AprilTags, see the FTC-DOCS link below: - * https://ftc-docs.firstinspires.org/en/latest/apriltag/vision_portal/apriltag_intro/apriltag-intro.html - * - * In this sample, any visible tag ID will be detected and displayed, but only tags that are included in the default - * "TagLibrary" will have their position and orientation information displayed. This default TagLibrary contains - * the current Season's AprilTags and a small set of "test Tags" in the high number range. - * - * When an AprilTag in the TagLibrary is detected, the SDK provides location and orientation of the tag, relative to the camera. - * This information is provided in the "ftcPose" member of the returned "detection", and is explained in the ftc-docs page linked below. - * https://ftc-docs.firstinspires.org/apriltag-detection-values - * - * To experiment with using AprilTags to navigate, try out these two driving samples: - * RobotAutoDriveToAprilTagOmni and RobotAutoDriveToAprilTagTank - * - * There are many "default" VisionPortal and AprilTag configuration parameters that may be overridden if desired. - * These default parameters are shown as comments in the code below. - * - * Use Android Studio to Copy this Class, and Paste it into your team's code folder with a new name. - * Remove or comment out the @Disabled line to add this OpMode to the Driver Station OpMode list. - */ -@TeleOp(name = "Concept: AprilTag", group = "Concept") -@Disabled -public class ConceptAprilTag extends LinearOpMode { - - private static final boolean USE_WEBCAM = true; // true for webcam, false for phone camera - - /** - * The variable to store our instance of the AprilTag processor. - */ - private AprilTagProcessor aprilTag; - - /** - * The variable to store our instance of the vision portal. - */ - private VisionPortal visionPortal; - - @Override - public void runOpMode() { - - initAprilTag(); - - // Wait for the DS start button to be touched. - telemetry.addData("DS preview on/off", "3 dots, Camera Stream"); - telemetry.addData(">", "Touch START to start OpMode"); - telemetry.update(); - waitForStart(); - - if (opModeIsActive()) { - while (opModeIsActive()) { - - telemetryAprilTag(); - - // Push telemetry to the Driver Station. - telemetry.update(); - - // Save CPU resources; can resume streaming when needed. - if (gamepad1.dpad_down) { - visionPortal.stopStreaming(); - } else if (gamepad1.dpad_up) { - visionPortal.resumeStreaming(); - } - - // Share the CPU. - sleep(20); - } - } - - // Save more CPU resources when camera is no longer needed. - visionPortal.close(); - - } // end method runOpMode() - - /** - * Initialize the AprilTag processor. - */ - private void initAprilTag() { - - // Create the AprilTag processor. - aprilTag = new AprilTagProcessor.Builder() - - // The following default settings are available to un-comment and edit as needed. - //.setDrawAxes(false) - //.setDrawCubeProjection(false) - //.setDrawTagOutline(true) - //.setTagFamily(AprilTagProcessor.TagFamily.TAG_36h11) - //.setTagLibrary(AprilTagGameDatabase.getCenterStageTagLibrary()) - //.setOutputUnits(DistanceUnit.INCH, AngleUnit.DEGREES) - - // == CAMERA CALIBRATION == - // If you do not manually specify calibration parameters, the SDK will attempt - // to load a predefined calibration for your camera. - //.setLensIntrinsics(578.272, 578.272, 402.145, 221.506) - // ... these parameters are fx, fy, cx, cy. - - .build(); - - // Adjust Image Decimation to trade-off detection-range for detection-rate. - // eg: Some typical detection data using a Logitech C920 WebCam - // Decimation = 1 .. Detect 2" Tag from 10 feet away at 10 Frames per second - // Decimation = 2 .. Detect 2" Tag from 6 feet away at 22 Frames per second - // Decimation = 3 .. Detect 2" Tag from 4 feet away at 30 Frames Per Second (default) - // Decimation = 3 .. Detect 5" Tag from 10 feet away at 30 Frames Per Second (default) - // Note: Decimation can be changed on-the-fly to adapt during a match. - //aprilTag.setDecimation(3); - - // Create the vision portal by using a builder. - VisionPortal.Builder builder = new VisionPortal.Builder(); - - // Set the camera (webcam vs. built-in RC phone camera). - if (USE_WEBCAM) { - builder.setCamera(hardwareMap.get(WebcamName.class, "Webcam 1")); - } else { - builder.setCamera(BuiltinCameraDirection.BACK); - } - - // Choose a camera resolution. Not all cameras support all resolutions. - //builder.setCameraResolution(new Size(640, 480)); - - // Enable the RC preview (LiveView). Set "false" to omit camera monitoring. - //builder.enableLiveView(true); - - // Set the stream format; MJPEG uses less bandwidth than default YUY2. - //builder.setStreamFormat(VisionPortal.StreamFormat.YUY2); - - // Choose whether or not LiveView stops if no processors are enabled. - // If set "true", monitor shows solid orange screen if no processors enabled. - // If set "false", monitor shows camera view without annotations. - //builder.setAutoStopLiveView(false); - - // Set and enable the processor. - builder.addProcessor(aprilTag); - - // Build the Vision Portal, using the above settings. - visionPortal = builder.build(); - - // Disable or re-enable the aprilTag processor at any time. - //visionPortal.setProcessorEnabled(aprilTag, true); - - } // end method initAprilTag() - - - /** - * Add telemetry about AprilTag detections. - */ - private void telemetryAprilTag() { - - List currentDetections = aprilTag.getDetections(); - telemetry.addData("# AprilTags Detected", currentDetections.size()); - - // Step through the list of detections and display info for each one. - for (AprilTagDetection detection : currentDetections) { - if (detection.metadata != null) { - telemetry.addLine(String.format("\n==== (ID %d) %s", detection.id, detection.metadata.name)); - telemetry.addLine(String.format("XYZ %6.1f %6.1f %6.1f (inch)", detection.ftcPose.x, detection.ftcPose.y, detection.ftcPose.z)); - telemetry.addLine(String.format("PRY %6.1f %6.1f %6.1f (deg)", detection.ftcPose.pitch, detection.ftcPose.roll, detection.ftcPose.yaw)); - telemetry.addLine(String.format("RBE %6.1f %6.1f %6.1f (inch, deg, deg)", detection.ftcPose.range, detection.ftcPose.bearing, detection.ftcPose.elevation)); - } else { - telemetry.addLine(String.format("\n==== (ID %d) Unknown", detection.id)); - telemetry.addLine(String.format("Center %6.0f %6.0f (pixels)", detection.center.x, detection.center.y)); - } - } // end for() loop - - // Add "key" information to telemetry - telemetry.addLine("\nkey:\nXYZ = X (Right), Y (Forward), Z (Up) dist."); - telemetry.addLine("PRY = Pitch, Roll & Yaw (XYZ Rotation)"); - telemetry.addLine("RBE = Range, Bearing & Elevation"); - - } // end method telemetryAprilTag() - -} // end class diff --git a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptAprilTagEasy.java b/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptAprilTagEasy.java deleted file mode 100644 index 12dcf6e..0000000 --- a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptAprilTagEasy.java +++ /dev/null @@ -1,163 +0,0 @@ -/* Copyright (c) 2023 FIRST. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted (subject to the limitations in the disclaimer below) provided that - * the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list - * of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this - * list of conditions and the following disclaimer in the documentation and/or - * other materials provided with the distribution. - * - * Neither the name of FIRST nor the names of its contributors may be used to endorse or - * promote products derived from this software without specific prior written permission. - * - * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS - * LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.firstinspires.ftc.robotcontroller.external.samples; - -import com.qualcomm.robotcore.eventloop.opmode.Disabled; -import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode; -import com.qualcomm.robotcore.eventloop.opmode.TeleOp; -import org.firstinspires.ftc.robotcore.external.hardware.camera.BuiltinCameraDirection; -import org.firstinspires.ftc.robotcore.external.hardware.camera.WebcamName; -import org.firstinspires.ftc.vision.VisionPortal; -import org.firstinspires.ftc.vision.apriltag.AprilTagDetection; -import org.firstinspires.ftc.vision.apriltag.AprilTagProcessor; - -import java.util.List; - -/* - * This OpMode illustrates the basics of AprilTag recognition and pose estimation, using - * the easy way. - * - * For an introduction to AprilTags, see the FTC-DOCS link below: - * https://ftc-docs.firstinspires.org/en/latest/apriltag/vision_portal/apriltag_intro/apriltag-intro.html - * - * In this sample, any visible tag ID will be detected and displayed, but only tags that are included in the default - * "TagLibrary" will have their position and orientation information displayed. This default TagLibrary contains - * the current Season's AprilTags and a small set of "test Tags" in the high number range. - * - * When an AprilTag in the TagLibrary is detected, the SDK provides location and orientation of the tag, relative to the camera. - * This information is provided in the "ftcPose" member of the returned "detection", and is explained in the ftc-docs page linked below. - * https://ftc-docs.firstinspires.org/apriltag-detection-values - * - * To experiment with using AprilTags to navigate, try out these two driving samples: - * RobotAutoDriveToAprilTagOmni and RobotAutoDriveToAprilTagTank - * - * Use Android Studio to Copy this Class, and Paste it into your team's code folder with a new name. - * Remove or comment out the @Disabled line to add this OpMode to the Driver Station OpMode list. - */ -@TeleOp(name = "Concept: AprilTag Easy", group = "Concept") -@Disabled -public class ConceptAprilTagEasy extends LinearOpMode { - - private static final boolean USE_WEBCAM = true; // true for webcam, false for phone camera - - /** - * The variable to store our instance of the AprilTag processor. - */ - private AprilTagProcessor aprilTag; - - /** - * The variable to store our instance of the vision portal. - */ - private VisionPortal visionPortal; - - @Override - public void runOpMode() { - - initAprilTag(); - - // Wait for the DS start button to be touched. - telemetry.addData("DS preview on/off", "3 dots, Camera Stream"); - telemetry.addData(">", "Touch START to start OpMode"); - telemetry.update(); - waitForStart(); - - if (opModeIsActive()) { - while (opModeIsActive()) { - - telemetryAprilTag(); - - // Push telemetry to the Driver Station. - telemetry.update(); - - // Save CPU resources; can resume streaming when needed. - if (gamepad1.dpad_down) { - visionPortal.stopStreaming(); - } else if (gamepad1.dpad_up) { - visionPortal.resumeStreaming(); - } - - // Share the CPU. - sleep(20); - } - } - - // Save more CPU resources when camera is no longer needed. - visionPortal.close(); - - } // end method runOpMode() - - /** - * Initialize the AprilTag processor. - */ - private void initAprilTag() { - - // Create the AprilTag processor the easy way. - aprilTag = AprilTagProcessor.easyCreateWithDefaults(); - - // Create the vision portal the easy way. - if (USE_WEBCAM) { - visionPortal = VisionPortal.easyCreateWithDefaults( - hardwareMap.get(WebcamName.class, "Webcam 1"), aprilTag); - } else { - visionPortal = VisionPortal.easyCreateWithDefaults( - BuiltinCameraDirection.BACK, aprilTag); - } - - } // end method initAprilTag() - - /** - * Add telemetry about AprilTag detections. - */ - private void telemetryAprilTag() { - - List currentDetections = aprilTag.getDetections(); - telemetry.addData("# AprilTags Detected", currentDetections.size()); - - // Step through the list of detections and display info for each one. - for (AprilTagDetection detection : currentDetections) { - if (detection.metadata != null) { - telemetry.addLine(String.format("\n==== (ID %d) %s", detection.id, detection.metadata.name)); - telemetry.addLine(String.format("XYZ %6.1f %6.1f %6.1f (inch)", detection.ftcPose.x, detection.ftcPose.y, detection.ftcPose.z)); - telemetry.addLine(String.format("PRY %6.1f %6.1f %6.1f (deg)", detection.ftcPose.pitch, detection.ftcPose.roll, detection.ftcPose.yaw)); - telemetry.addLine(String.format("RBE %6.1f %6.1f %6.1f (inch, deg, deg)", detection.ftcPose.range, detection.ftcPose.bearing, detection.ftcPose.elevation)); - } else { - telemetry.addLine(String.format("\n==== (ID %d) Unknown", detection.id)); - telemetry.addLine(String.format("Center %6.0f %6.0f (pixels)", detection.center.x, detection.center.y)); - } - } // end for() loop - - // Add "key" information to telemetry - telemetry.addLine("\nkey:\nXYZ = X (Right), Y (Forward), Z (Up) dist."); - telemetry.addLine("PRY = Pitch, Roll & Yaw (XYZ Rotation)"); - telemetry.addLine("RBE = Range, Bearing & Elevation"); - - } // end method telemetryAprilTag() - -} // end class diff --git a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptAprilTagLocalization.java b/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptAprilTagLocalization.java deleted file mode 100644 index d90261e..0000000 --- a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptAprilTagLocalization.java +++ /dev/null @@ -1,247 +0,0 @@ -/* Copyright (c) 2024 Dryw Wade. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted (subject to the limitations in the disclaimer below) provided that - * the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list - * of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this - * list of conditions and the following disclaimer in the documentation and/or - * other materials provided with the distribution. - * - * Neither the name of FIRST nor the names of its contributors may be used to endorse or - * promote products derived from this software without specific prior written permission. - * - * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS - * LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.firstinspires.ftc.robotcontroller.external.samples; - -import com.qualcomm.robotcore.eventloop.opmode.Disabled; -import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode; -import com.qualcomm.robotcore.eventloop.opmode.TeleOp; - -import org.firstinspires.ftc.robotcore.external.hardware.camera.BuiltinCameraDirection; -import org.firstinspires.ftc.robotcore.external.hardware.camera.WebcamName; -import org.firstinspires.ftc.robotcore.external.navigation.AngleUnit; -import org.firstinspires.ftc.robotcore.external.navigation.DistanceUnit; -import org.firstinspires.ftc.robotcore.external.navigation.Position; -import org.firstinspires.ftc.robotcore.external.navigation.YawPitchRollAngles; -import org.firstinspires.ftc.vision.VisionPortal; -import org.firstinspires.ftc.vision.apriltag.AprilTagDetection; -import org.firstinspires.ftc.vision.apriltag.AprilTagProcessor; - -import java.util.List; - -/* - * This OpMode illustrates the basics of AprilTag based localization. - * - * For an introduction to AprilTags, see the FTC-DOCS link below: - * https://ftc-docs.firstinspires.org/en/latest/apriltag/vision_portal/apriltag_intro/apriltag-intro.html - * - * In this sample, any visible tag ID will be detected and displayed, but only tags that are included in the default - * "TagLibrary" will be used to compute the robot's location and orientation. This default TagLibrary contains - * the current Season's AprilTags and a small set of "test Tags" in the high number range. - * - * When an AprilTag in the TagLibrary is detected, the SDK provides location and orientation of the robot, relative to the field origin. - * This information is provided in the "robotPose" member of the returned "detection". - * - * To learn about the Field Coordinate System that is defined for FTC (and used by this OpMode), see the FTC-DOCS link below: - * https://ftc-docs.firstinspires.org/en/latest/game_specific_resources/field_coordinate_system/field-coordinate-system.html - * - * Use Android Studio to Copy this Class, and Paste it into your team's code folder with a new name. - * Remove or comment out the @Disabled line to add this OpMode to the Driver Station OpMode list. - */ -@TeleOp(name = "Concept: AprilTag Localization", group = "Concept") -@Disabled -public class ConceptAprilTagLocalization extends LinearOpMode { - - private static final boolean USE_WEBCAM = true; // true for webcam, false for phone camera - - /** - * Variables to store the position and orientation of the camera on the robot. Setting these - * values requires a definition of the axes of the camera and robot: - * - * Camera axes: - * Origin location: Center of the lens - * Axes orientation: +x right, +y down, +z forward (from camera's perspective) - * - * Robot axes (this is typical, but you can define this however you want): - * Origin location: Center of the robot at field height - * Axes orientation: +x right, +y forward, +z upward - * - * Position: - * If all values are zero (no translation), that implies the camera is at the center of the - * robot. Suppose your camera is positioned 5 inches to the left, 7 inches forward, and 12 - * inches above the ground - you would need to set the position to (-5, 7, 12). - * - * Orientation: - * If all values are zero (no rotation), that implies the camera is pointing straight up. In - * most cases, you'll need to set the pitch to -90 degrees (rotation about the x-axis), meaning - * the camera is horizontal. Use a yaw of 0 if the camera is pointing forwards, +90 degrees if - * it's pointing straight left, -90 degrees for straight right, etc. You can also set the roll - * to +/-90 degrees if it's vertical, or 180 degrees if it's upside-down. - */ - private Position cameraPosition = new Position(DistanceUnit.INCH, - 0, 0, 0, 0); - private YawPitchRollAngles cameraOrientation = new YawPitchRollAngles(AngleUnit.DEGREES, - 0, -90, 0, 0); - - /** - * The variable to store our instance of the AprilTag processor. - */ - private AprilTagProcessor aprilTag; - - /** - * The variable to store our instance of the vision portal. - */ - private VisionPortal visionPortal; - - @Override - public void runOpMode() { - - initAprilTag(); - - // Wait for the DS start button to be touched. - telemetry.addData("DS preview on/off", "3 dots, Camera Stream"); - telemetry.addData(">", "Touch START to start OpMode"); - telemetry.update(); - waitForStart(); - - while (opModeIsActive()) { - - telemetryAprilTag(); - - // Push telemetry to the Driver Station. - telemetry.update(); - - // Save CPU resources; can resume streaming when needed. - if (gamepad1.dpad_down) { - visionPortal.stopStreaming(); - } else if (gamepad1.dpad_up) { - visionPortal.resumeStreaming(); - } - - // Share the CPU. - sleep(20); - } - - // Save more CPU resources when camera is no longer needed. - visionPortal.close(); - - } // end method runOpMode() - - /** - * Initialize the AprilTag processor. - */ - private void initAprilTag() { - - // Create the AprilTag processor. - aprilTag = new AprilTagProcessor.Builder() - - // The following default settings are available to un-comment and edit as needed. - //.setDrawAxes(false) - //.setDrawCubeProjection(false) - //.setDrawTagOutline(true) - //.setTagFamily(AprilTagProcessor.TagFamily.TAG_36h11) - //.setTagLibrary(AprilTagGameDatabase.getCenterStageTagLibrary()) - //.setOutputUnits(DistanceUnit.INCH, AngleUnit.DEGREES) - .setCameraPose(cameraPosition, cameraOrientation) - - // == CAMERA CALIBRATION == - // If you do not manually specify calibration parameters, the SDK will attempt - // to load a predefined calibration for your camera. - //.setLensIntrinsics(578.272, 578.272, 402.145, 221.506) - // ... these parameters are fx, fy, cx, cy. - - .build(); - - // Adjust Image Decimation to trade-off detection-range for detection-rate. - // eg: Some typical detection data using a Logitech C920 WebCam - // Decimation = 1 .. Detect 2" Tag from 10 feet away at 10 Frames per second - // Decimation = 2 .. Detect 2" Tag from 6 feet away at 22 Frames per second - // Decimation = 3 .. Detect 2" Tag from 4 feet away at 30 Frames Per Second (default) - // Decimation = 3 .. Detect 5" Tag from 10 feet away at 30 Frames Per Second (default) - // Note: Decimation can be changed on-the-fly to adapt during a match. - //aprilTag.setDecimation(3); - - // Create the vision portal by using a builder. - VisionPortal.Builder builder = new VisionPortal.Builder(); - - // Set the camera (webcam vs. built-in RC phone camera). - if (USE_WEBCAM) { - builder.setCamera(hardwareMap.get(WebcamName.class, "Webcam 1")); - } else { - builder.setCamera(BuiltinCameraDirection.BACK); - } - - // Choose a camera resolution. Not all cameras support all resolutions. - //builder.setCameraResolution(new Size(640, 480)); - - // Enable the RC preview (LiveView). Set "false" to omit camera monitoring. - //builder.enableLiveView(true); - - // Set the stream format; MJPEG uses less bandwidth than default YUY2. - //builder.setStreamFormat(VisionPortal.StreamFormat.YUY2); - - // Choose whether or not LiveView stops if no processors are enabled. - // If set "true", monitor shows solid orange screen if no processors enabled. - // If set "false", monitor shows camera view without annotations. - //builder.setAutoStopLiveView(false); - - // Set and enable the processor. - builder.addProcessor(aprilTag); - - // Build the Vision Portal, using the above settings. - visionPortal = builder.build(); - - // Disable or re-enable the aprilTag processor at any time. - //visionPortal.setProcessorEnabled(aprilTag, true); - - } // end method initAprilTag() - - /** - * Add telemetry about AprilTag detections. - */ - private void telemetryAprilTag() { - - List currentDetections = aprilTag.getDetections(); - telemetry.addData("# AprilTags Detected", currentDetections.size()); - - // Step through the list of detections and display info for each one. - for (AprilTagDetection detection : currentDetections) { - if (detection.metadata != null) { - telemetry.addLine(String.format("\n==== (ID %d) %s", detection.id, detection.metadata.name)); - telemetry.addLine(String.format("XYZ %6.1f %6.1f %6.1f (inch)", - detection.robotPose.getPosition().x, - detection.robotPose.getPosition().y, - detection.robotPose.getPosition().z)); - telemetry.addLine(String.format("PRY %6.1f %6.1f %6.1f (deg)", - detection.robotPose.getOrientation().getPitch(AngleUnit.DEGREES), - detection.robotPose.getOrientation().getRoll(AngleUnit.DEGREES), - detection.robotPose.getOrientation().getYaw(AngleUnit.DEGREES))); - } else { - telemetry.addLine(String.format("\n==== (ID %d) Unknown", detection.id)); - telemetry.addLine(String.format("Center %6.0f %6.0f (pixels)", detection.center.x, detection.center.y)); - } - } // end for() loop - - // Add "key" information to telemetry - telemetry.addLine("\nkey:\nXYZ = X (Right), Y (Forward), Z (Up) dist."); - telemetry.addLine("PRY = Pitch, Roll & Yaw (XYZ Rotation)"); - - } // end method telemetryAprilTag() - -} // end class diff --git a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptAprilTagMultiPortal.java b/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptAprilTagMultiPortal.java deleted file mode 100644 index da5cc3e..0000000 --- a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptAprilTagMultiPortal.java +++ /dev/null @@ -1,104 +0,0 @@ -/* Copyright (c) 2024 FIRST. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted (subject to the limitations in the disclaimer below) provided that - * the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list - * of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this - * list of conditions and the following disclaimer in the documentation and/or - * other materials provided with the distribution. - * - * Neither the name of FIRST nor the names of its contributors may be used to endorse or - * promote products derived from this software without specific prior written permission. - * - * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS - * LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.firstinspires.ftc.robotcontroller.external.samples; - -import com.qualcomm.robotcore.eventloop.opmode.Disabled; -import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode; -import com.qualcomm.robotcore.eventloop.opmode.TeleOp; - -import org.firstinspires.ftc.robotcore.external.hardware.camera.BuiltinCameraDirection; -import org.firstinspires.ftc.robotcore.external.hardware.camera.WebcamName; -import org.firstinspires.ftc.vision.VisionPortal; -import org.firstinspires.ftc.vision.apriltag.AprilTagProcessor; - -/** - * This OpMode demonstrates the basics of using multiple vision portals simultaneously - */ -@TeleOp(name = "Concept: AprilTagMultiPortal", group = "Concept") -@Disabled -public class ConceptAprilTagMultiPortal extends LinearOpMode -{ - VisionPortal portal1; - VisionPortal portal2; - - AprilTagProcessor aprilTagProcessor1; - AprilTagProcessor aprilTagProcessor2; - - @Override - public void runOpMode() throws InterruptedException - { - // Because we want to show two camera feeds simultaneously, we need to inform - // the SDK that we want it to split the camera monitor area into two smaller - // areas for us. It will then give us View IDs which we can pass to the individual - // vision portals to allow them to properly hook into the UI in tandem. - int[] viewIds = VisionPortal.makeMultiPortalView(2, VisionPortal.MultiPortalLayout.VERTICAL); - - // We extract the two view IDs from the array to make our lives a little easier later. - // NB: the array is 2 long because we asked for 2 portals up above. - int portal1ViewId = viewIds[0]; - int portal2ViewId = viewIds[1]; - - // If we want to run AprilTag detection on two portals simultaneously, - // we need to create two distinct instances of the AprilTag processor, - // one for each portal. If you want to see more detail about different - // options that you have when creating these processors, go check out - // the ConceptAprilTag OpMode. - aprilTagProcessor1 = AprilTagProcessor.easyCreateWithDefaults(); - aprilTagProcessor2 = AprilTagProcessor.easyCreateWithDefaults(); - - // Now we build both portals. The CRITICAL thing to notice here is the call to - // setLiveViewContainerId(), where we pass in the IDs we received earlier from - // makeMultiPortalView(). - portal1 = new VisionPortal.Builder() - .setCamera(hardwareMap.get(WebcamName.class, "Webcam 1")) - .setLiveViewContainerId(portal1ViewId) - .addProcessor(aprilTagProcessor1) - .build(); - portal2 = new VisionPortal.Builder() - .setCamera(hardwareMap.get(WebcamName.class, "Webcam 2")) - .setLiveViewContainerId(portal2ViewId) - .addProcessor(aprilTagProcessor2) - .build(); - - waitForStart(); - - // Main Loop - while (opModeIsActive()) - { - // Just show some basic telemetry to demonstrate both processors are working in parallel - // on their respective cameras. If you want to see more detail about the information you - // can get back from the processor, you should look at ConceptAprilTag. - telemetry.addData("Number of tags in Camera 1", aprilTagProcessor1.getDetections().size()); - telemetry.addData("Number of tags in Camera 2", aprilTagProcessor2.getDetections().size()); - telemetry.update(); - sleep(20); - } - } -} diff --git a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptAprilTagOptimizeExposure.java b/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptAprilTagOptimizeExposure.java deleted file mode 100644 index adee952..0000000 --- a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptAprilTagOptimizeExposure.java +++ /dev/null @@ -1,246 +0,0 @@ -/* Copyright (c) 2023 FIRST. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted (subject to the limitations in the disclaimer below) provided that - * the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list - * of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this - * list of conditions and the following disclaimer in the documentation and/or - * other materials provided with the distribution. - * - * Neither the name of FIRST nor the names of its contributors may be used to endorse or - * promote products derived from this software without specific prior written permission. - * - * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS - * LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.firstinspires.ftc.robotcontroller.external.samples; - -import com.qualcomm.robotcore.eventloop.opmode.Disabled; -import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode; -import com.qualcomm.robotcore.eventloop.opmode.TeleOp; -import com.qualcomm.robotcore.util.Range; -import org.firstinspires.ftc.robotcore.external.hardware.camera.WebcamName; -import org.firstinspires.ftc.robotcore.external.hardware.camera.controls.ExposureControl; -import org.firstinspires.ftc.robotcore.external.hardware.camera.controls.GainControl; -import org.firstinspires.ftc.vision.VisionPortal; -import org.firstinspires.ftc.vision.apriltag.AprilTagDetection; -import org.firstinspires.ftc.vision.apriltag.AprilTagProcessor; - -import java.util.List; -import java.util.concurrent.TimeUnit; - -/* - * This OpMode determines the best Exposure for minimizing image motion-blur on a Webcam - * Note that it is not possible to control the exposure for a Phone Camera, so if you are using a Phone for the Robot Controller - * this OpMode/Feature only applies to an externally connected Webcam - * - * The goal is to determine the smallest (shortest) Exposure value that still provides reliable Tag Detection. - * Starting with the minimum Exposure and maximum Gain, the exposure is slowly increased until the Tag is - * detected reliably from the likely operational distance. - * - * - * The best way to run this optimization is to view the camera preview screen while changing the exposure and gains. - * - * To do this, you need to view the RobotController screen directly (not from Driver Station) - * This can be done directly from a RC phone screen (if you are using an external Webcam), but for a Control Hub you must either plug an - * HDMI monitor into the Control Hub HDMI port, or use an external viewer program like ScrCpy (https://scrcpy.org/) - * - * Use Android Studio to Copy this Class, and Paste it into the TeamCode/src/main/java/org/firstinspires/ftc/teamcode folder. - * Remove or comment out the @Disabled line to add this OpMode to the Driver Station OpMode list. - */ - -@TeleOp(name="Optimize AprilTag Exposure", group = "Concept") -@Disabled -public class ConceptAprilTagOptimizeExposure extends LinearOpMode -{ - private VisionPortal visionPortal = null; // Used to manage the video source. - private AprilTagProcessor aprilTag; // Used for managing the AprilTag detection process. - private int myExposure ; - private int minExposure ; - private int maxExposure ; - private int myGain ; - private int minGain ; - private int maxGain ; - - boolean thisExpUp = false; - boolean thisExpDn = false; - boolean thisGainUp = false; - boolean thisGainDn = false; - - boolean lastExpUp = false; - boolean lastExpDn = false; - boolean lastGainUp = false; - boolean lastGainDn = false; - @Override public void runOpMode() - { - // Initialize the Apriltag Detection process - initAprilTag(); - - // Establish Min and Max Gains and Exposure. Then set a low exposure with high gain - getCameraSetting(); - myExposure = Math.min(5, minExposure); - myGain = maxGain; - setManualExposure(myExposure, myGain); - - // Wait for the match to begin. - telemetry.addData("Camera preview on/off", "3 dots, Camera Stream"); - telemetry.addData(">", "Touch START to start OpMode"); - telemetry.update(); - waitForStart(); - - while (opModeIsActive()) - { - telemetry.addLine("Find lowest Exposure that gives reliable detection."); - telemetry.addLine("Use Left bump/trig to adjust Exposure."); - telemetry.addLine("Use Right bump/trig to adjust Gain.\n"); - - // Display how many Tags Detected - List currentDetections = aprilTag.getDetections(); - int numTags = currentDetections.size(); - if (numTags > 0 ) - telemetry.addData("Tag", "####### %d Detected ######", currentDetections.size()); - else - telemetry.addData("Tag", "----------- none - ----------"); - - telemetry.addData("Exposure","%d (%d - %d)", myExposure, minExposure, maxExposure); - telemetry.addData("Gain","%d (%d - %d)", myGain, minGain, maxGain); - telemetry.update(); - - // check to see if we need to change exposure or gain. - thisExpUp = gamepad1.left_bumper; - thisExpDn = gamepad1.left_trigger > 0.25; - thisGainUp = gamepad1.right_bumper; - thisGainDn = gamepad1.right_trigger > 0.25; - - // look for clicks to change exposure - if (thisExpUp && !lastExpUp) { - myExposure = Range.clip(myExposure + 1, minExposure, maxExposure); - setManualExposure(myExposure, myGain); - } else if (thisExpDn && !lastExpDn) { - myExposure = Range.clip(myExposure - 1, minExposure, maxExposure); - setManualExposure(myExposure, myGain); - } - - // look for clicks to change the gain - if (thisGainUp && !lastGainUp) { - myGain = Range.clip(myGain + 1, minGain, maxGain ); - setManualExposure(myExposure, myGain); - } else if (thisGainDn && !lastGainDn) { - myGain = Range.clip(myGain - 1, minGain, maxGain ); - setManualExposure(myExposure, myGain); - } - - lastExpUp = thisExpUp; - lastExpDn = thisExpDn; - lastGainUp = thisGainUp; - lastGainDn = thisGainDn; - - sleep(20); - } - } - - /** - * Initialize the AprilTag processor. - */ - private void initAprilTag() { - // Create the AprilTag processor by using a builder. - aprilTag = new AprilTagProcessor.Builder().build(); - - // Create the WEBCAM vision portal by using a builder. - visionPortal = new VisionPortal.Builder() - .setCamera(hardwareMap.get(WebcamName.class, "Webcam 1")) - .addProcessor(aprilTag) - .build(); - } - - /* - Manually set the camera gain and exposure. - Can only be called AFTER calling initAprilTag(); - Returns true if controls are set. - */ - private boolean setManualExposure(int exposureMS, int gain) { - // Ensure Vision Portal has been setup. - if (visionPortal == null) { - return false; - } - - // Wait for the camera to be open - if (visionPortal.getCameraState() != VisionPortal.CameraState.STREAMING) { - telemetry.addData("Camera", "Waiting"); - telemetry.update(); - while (!isStopRequested() && (visionPortal.getCameraState() != VisionPortal.CameraState.STREAMING)) { - sleep(20); - } - telemetry.addData("Camera", "Ready"); - telemetry.update(); - } - - // Set camera controls unless we are stopping. - if (!isStopRequested()) - { - // Set exposure. Make sure we are in Manual Mode for these values to take effect. - ExposureControl exposureControl = visionPortal.getCameraControl(ExposureControl.class); - if (exposureControl.getMode() != ExposureControl.Mode.Manual) { - exposureControl.setMode(ExposureControl.Mode.Manual); - sleep(50); - } - exposureControl.setExposure((long)exposureMS, TimeUnit.MILLISECONDS); - sleep(20); - - // Set Gain. - GainControl gainControl = visionPortal.getCameraControl(GainControl.class); - gainControl.setGain(gain); - sleep(20); - return (true); - } else { - return (false); - } - } - - /* - Read this camera's minimum and maximum Exposure and Gain settings. - Can only be called AFTER calling initAprilTag(); - */ - private void getCameraSetting() { - // Ensure Vision Portal has been setup. - if (visionPortal == null) { - return; - } - - // Wait for the camera to be open - if (visionPortal.getCameraState() != VisionPortal.CameraState.STREAMING) { - telemetry.addData("Camera", "Waiting"); - telemetry.update(); - while (!isStopRequested() && (visionPortal.getCameraState() != VisionPortal.CameraState.STREAMING)) { - sleep(20); - } - telemetry.addData("Camera", "Ready"); - telemetry.update(); - } - - // Get camera control values unless we are stopping. - if (!isStopRequested()) { - ExposureControl exposureControl = visionPortal.getCameraControl(ExposureControl.class); - minExposure = (int)exposureControl.getMinExposure(TimeUnit.MILLISECONDS) + 1; - maxExposure = (int)exposureControl.getMaxExposure(TimeUnit.MILLISECONDS); - - GainControl gainControl = visionPortal.getCameraControl(GainControl.class); - minGain = gainControl.getMinGain(); - maxGain = gainControl.getMaxGain(); - } - } -} diff --git a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptAprilTagSwitchableCameras.java b/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptAprilTagSwitchableCameras.java deleted file mode 100644 index 7ee1064..0000000 --- a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptAprilTagSwitchableCameras.java +++ /dev/null @@ -1,196 +0,0 @@ -/* Copyright (c) 2023 FIRST. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted (subject to the limitations in the disclaimer below) provided that - * the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list - * of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this - * list of conditions and the following disclaimer in the documentation and/or - * other materials provided with the distribution. - * - * Neither the name of FIRST nor the names of its contributors may be used to endorse or - * promote products derived from this software without specific prior written permission. - * - * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS - * LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.firstinspires.ftc.robotcontroller.external.samples; - -import com.qualcomm.robotcore.eventloop.opmode.Disabled; -import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode; -import com.qualcomm.robotcore.eventloop.opmode.TeleOp; -import org.firstinspires.ftc.robotcore.external.ClassFactory; -import org.firstinspires.ftc.robotcore.external.hardware.camera.CameraName; -import org.firstinspires.ftc.robotcore.external.hardware.camera.WebcamName; -import org.firstinspires.ftc.vision.VisionPortal; -import org.firstinspires.ftc.vision.VisionPortal.CameraState; -import org.firstinspires.ftc.vision.apriltag.AprilTagDetection; -import org.firstinspires.ftc.vision.apriltag.AprilTagProcessor; - -import java.util.List; - -/* - * This OpMode illustrates the basics of AprilTag recognition and pose estimation, using - * two webcams. - * - * Use Android Studio to Copy this Class, and Paste it into your team's code folder with a new name. - * Remove or comment out the @Disabled line to add this OpMode to the Driver Station OpMode list. - */ -@TeleOp(name = "Concept: AprilTag Switchable Cameras", group = "Concept") -@Disabled -public class ConceptAprilTagSwitchableCameras extends LinearOpMode { - - /* - * Variables used for switching cameras. - */ - private WebcamName webcam1, webcam2; - private boolean oldLeftBumper; - private boolean oldRightBumper; - - /** - * The variable to store our instance of the AprilTag processor. - */ - private AprilTagProcessor aprilTag; - - /** - * The variable to store our instance of the vision portal. - */ - private VisionPortal visionPortal; - - @Override - public void runOpMode() { - - initAprilTag(); - - // Wait for the DS start button to be touched. - telemetry.addData("DS preview on/off", "3 dots, Camera Stream"); - telemetry.addData(">", "Touch START to start OpMode"); - telemetry.update(); - waitForStart(); - - if (opModeIsActive()) { - while (opModeIsActive()) { - - telemetryCameraSwitching(); - telemetryAprilTag(); - - // Push telemetry to the Driver Station. - telemetry.update(); - - // Save CPU resources; can resume streaming when needed. - if (gamepad1.dpad_down) { - visionPortal.stopStreaming(); - } else if (gamepad1.dpad_up) { - visionPortal.resumeStreaming(); - } - - doCameraSwitching(); - - // Share the CPU. - sleep(20); - } - } - - // Save more CPU resources when camera is no longer needed. - visionPortal.close(); - - } // end runOpMode() - - /** - * Initialize the AprilTag processor. - */ - private void initAprilTag() { - - // Create the AprilTag processor by using a builder. - aprilTag = new AprilTagProcessor.Builder().build(); - - webcam1 = hardwareMap.get(WebcamName.class, "Webcam 1"); - webcam2 = hardwareMap.get(WebcamName.class, "Webcam 2"); - CameraName switchableCamera = ClassFactory.getInstance() - .getCameraManager().nameForSwitchableCamera(webcam1, webcam2); - - // Create the vision portal by using a builder. - visionPortal = new VisionPortal.Builder() - .setCamera(switchableCamera) - .addProcessor(aprilTag) - .build(); - - } // end method initAprilTag() - - /** - * Add telemetry about camera switching. - */ - private void telemetryCameraSwitching() { - - if (visionPortal.getActiveCamera().equals(webcam1)) { - telemetry.addData("activeCamera", "Webcam 1"); - telemetry.addData("Press RightBumper", "to switch to Webcam 2"); - } else { - telemetry.addData("activeCamera", "Webcam 2"); - telemetry.addData("Press LeftBumper", "to switch to Webcam 1"); - } - - } // end method telemetryCameraSwitching() - - /** - * Add telemetry about AprilTag detections. - */ - private void telemetryAprilTag() { - - List currentDetections = aprilTag.getDetections(); - telemetry.addData("# AprilTags Detected", currentDetections.size()); - - // Step through the list of detections and display info for each one. - for (AprilTagDetection detection : currentDetections) { - if (detection.metadata != null) { - telemetry.addLine(String.format("\n==== (ID %d) %s", detection.id, detection.metadata.name)); - telemetry.addLine(String.format("XYZ %6.1f %6.1f %6.1f (inch)", detection.ftcPose.x, detection.ftcPose.y, detection.ftcPose.z)); - telemetry.addLine(String.format("PRY %6.1f %6.1f %6.1f (deg)", detection.ftcPose.pitch, detection.ftcPose.roll, detection.ftcPose.yaw)); - telemetry.addLine(String.format("RBE %6.1f %6.1f %6.1f (inch, deg, deg)", detection.ftcPose.range, detection.ftcPose.bearing, detection.ftcPose.elevation)); - } else { - telemetry.addLine(String.format("\n==== (ID %d) Unknown", detection.id)); - telemetry.addLine(String.format("Center %6.0f %6.0f (pixels)", detection.center.x, detection.center.y)); - } - } // end for() loop - - // Add "key" information to telemetry - telemetry.addLine("\nkey:\nXYZ = X (Right), Y (Forward), Z (Up) dist."); - telemetry.addLine("PRY = Pitch, Roll & Yaw (XYZ Rotation)"); - telemetry.addLine("RBE = Range, Bearing & Elevation"); - - } // end method telemetryAprilTag() - - /** - * Set the active camera according to input from the gamepad. - */ - private void doCameraSwitching() { - if (visionPortal.getCameraState() == CameraState.STREAMING) { - // If the left bumper is pressed, use Webcam 1. - // If the right bumper is pressed, use Webcam 2. - boolean newLeftBumper = gamepad1.left_bumper; - boolean newRightBumper = gamepad1.right_bumper; - if (newLeftBumper && !oldLeftBumper) { - visionPortal.setActiveCamera(webcam1); - } else if (newRightBumper && !oldRightBumper) { - visionPortal.setActiveCamera(webcam2); - } - oldLeftBumper = newLeftBumper; - oldRightBumper = newRightBumper; - } - - } // end method doCameraSwitching() - -} // end class diff --git a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptExploringIMUOrientation.java b/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptExploringIMUOrientation.java deleted file mode 100644 index 751d54f..0000000 --- a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptExploringIMUOrientation.java +++ /dev/null @@ -1,184 +0,0 @@ -/* -Copyright (c) 2022 REV Robotics, FIRST - -All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted (subject to the limitations in the disclaimer below) provided that -the following conditions are met: - -Redistributions of source code must retain the above copyright notice, this list -of conditions and the following disclaimer. - -Redistributions in binary form must reproduce the above copyright notice, this -list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. - -Neither the name of REV Robotics nor the names of its contributors may be used to -endorse or promote products derived from this software without specific prior -written permission. - -NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS -LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR -TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF -THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ -package org.firstinspires.ftc.robotcontroller.external.samples; - -import com.qualcomm.hardware.rev.RevHubOrientationOnRobot; -import com.qualcomm.robotcore.eventloop.opmode.Disabled; -import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode; -import com.qualcomm.robotcore.eventloop.opmode.TeleOp; -import com.qualcomm.robotcore.hardware.IMU; -import org.firstinspires.ftc.robotcore.external.navigation.AngleUnit; -import org.firstinspires.ftc.robotcore.external.navigation.AngularVelocity; -import org.firstinspires.ftc.robotcore.external.navigation.YawPitchRollAngles; - -/* - * This OpMode demonstrates the impact of setting the IMU orientation correctly or incorrectly. This - * code assumes there is an IMU configured with the name "imu". - * - * Note: This OpMode is more of a tool than a code sample. The User Interface portion of this code - * goes beyond simply showing how to interface to the IMU.
- * For a minimal example of interfacing to an IMU, please see the SensorIMUOrthogonal or SensorIMUNonOrthogonal sample OpModes. - * - * This OpMode enables you to re-specify the Hub Mounting orientation dynamically by using gamepad controls. - * While doing so, the sample will display how Pitch, Roll and Yaw angles change as the hub is moved. - * - * The gamepad controls let you change the two parameters that specify how the Control/Expansion Hub is mounted.
- * The first parameter specifies which direction the printed logo on the Hub is pointing.
- * The second parameter specifies which direction the USB connector on the Hub is pointing.
- * All directions are relative to the robot, and left/right is as viewed from behind the robot. - * - * How will you know if you have chosen the correct Orientation? With the correct orientation - * parameters selected, pitch/roll/yaw should act as follows: - * - * Pitch value should INCREASE as the robot is tipped UP at the front. (Rotation about X)
- * Roll value should INCREASE as the robot is tipped UP at the left side. (Rotation about Y)
- * Yaw value should INCREASE as the robot is rotated Counter Clockwise. (Rotation about Z)
- * - * The Yaw can be reset (to zero) by pressing the Y button on the gamepad (Triangle on a PS4 controller) - * - * The rotational velocities should follow the change in corresponding axes. - */ - -@TeleOp(name="Concept: IMU Orientation", group="Concept") -@Disabled -public class ConceptExploringIMUOrientation extends LinearOpMode { - static RevHubOrientationOnRobot.LogoFacingDirection[] logoFacingDirections - = RevHubOrientationOnRobot.LogoFacingDirection.values(); - static RevHubOrientationOnRobot.UsbFacingDirection[] usbFacingDirections - = RevHubOrientationOnRobot.UsbFacingDirection.values(); - static int LAST_DIRECTION = logoFacingDirections.length - 1; - static float TRIGGER_THRESHOLD = 0.2f; - - IMU imu; - int logoFacingDirectionPosition; - int usbFacingDirectionPosition; - boolean orientationIsValid = true; - - @Override public void runOpMode() throws InterruptedException { - imu = hardwareMap.get(IMU.class, "imu"); - logoFacingDirectionPosition = 0; // Up - usbFacingDirectionPosition = 2; // Forward - - updateOrientation(); - - boolean justChangedLogoDirection = false; - boolean justChangedUsbDirection = false; - - // Loop until stop requested - while (!isStopRequested()) { - - // Check to see if Yaw reset is requested (Y button) - if (gamepad1.y) { - telemetry.addData("Yaw", "Resetting\n"); - imu.resetYaw(); - } else { - telemetry.addData("Yaw", "Press Y (triangle) on Gamepad to reset.\n"); - } - - // Check to see if new Logo Direction is requested - if (gamepad1.left_bumper || gamepad1.right_bumper) { - if (!justChangedLogoDirection) { - justChangedLogoDirection = true; - if (gamepad1.left_bumper) { - logoFacingDirectionPosition--; - if (logoFacingDirectionPosition < 0) { - logoFacingDirectionPosition = LAST_DIRECTION; - } - } else { - logoFacingDirectionPosition++; - if (logoFacingDirectionPosition > LAST_DIRECTION) { - logoFacingDirectionPosition = 0; - } - } - updateOrientation(); - } - } else { - justChangedLogoDirection = false; - } - - // Check to see if new USB Direction is requested - if (gamepad1.left_trigger > TRIGGER_THRESHOLD || gamepad1.right_trigger > TRIGGER_THRESHOLD) { - if (!justChangedUsbDirection) { - justChangedUsbDirection = true; - if (gamepad1.left_trigger > TRIGGER_THRESHOLD) { - usbFacingDirectionPosition--; - if (usbFacingDirectionPosition < 0) { - usbFacingDirectionPosition = LAST_DIRECTION; - } - } else { - usbFacingDirectionPosition++; - if (usbFacingDirectionPosition > LAST_DIRECTION) { - usbFacingDirectionPosition = 0; - } - } - updateOrientation(); - } - } else { - justChangedUsbDirection = false; - } - - // Display User instructions and IMU data - telemetry.addData("logo Direction (set with bumpers)", logoFacingDirections[logoFacingDirectionPosition]); - telemetry.addData("usb Direction (set with triggers)", usbFacingDirections[usbFacingDirectionPosition] + "\n"); - - if (orientationIsValid) { - YawPitchRollAngles orientation = imu.getRobotYawPitchRollAngles(); - AngularVelocity angularVelocity = imu.getRobotAngularVelocity(AngleUnit.DEGREES); - - telemetry.addData("Yaw (Z)", "%.2f Deg. (Heading)", orientation.getYaw(AngleUnit.DEGREES)); - telemetry.addData("Pitch (X)", "%.2f Deg.", orientation.getPitch(AngleUnit.DEGREES)); - telemetry.addData("Roll (Y)", "%.2f Deg.\n", orientation.getRoll(AngleUnit.DEGREES)); - telemetry.addData("Yaw (Z) velocity", "%.2f Deg/Sec", angularVelocity.zRotationRate); - telemetry.addData("Pitch (X) velocity", "%.2f Deg/Sec", angularVelocity.xRotationRate); - telemetry.addData("Roll (Y) velocity", "%.2f Deg/Sec", angularVelocity.yRotationRate); - } else { - telemetry.addData("Error", "Selected orientation on robot is invalid"); - } - - telemetry.update(); - } - } - - // apply any requested orientation changes. - void updateOrientation() { - RevHubOrientationOnRobot.LogoFacingDirection logo = logoFacingDirections[logoFacingDirectionPosition]; - RevHubOrientationOnRobot.UsbFacingDirection usb = usbFacingDirections[usbFacingDirectionPosition]; - try { - RevHubOrientationOnRobot orientationOnRobot = new RevHubOrientationOnRobot(logo, usb); - imu.initialize(new IMU.Parameters(orientationOnRobot)); - orientationIsValid = true; - } catch (IllegalArgumentException e) { - orientationIsValid = false; - } - } -} diff --git a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptExternalHardwareClass.java b/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptExternalHardwareClass.java deleted file mode 100644 index 7a721fe..0000000 --- a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptExternalHardwareClass.java +++ /dev/null @@ -1,142 +0,0 @@ -/* Copyright (c) 2022 FIRST. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted (subject to the limitations in the disclaimer below) provided that - * the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list - * of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this - * list of conditions and the following disclaimer in the documentation and/or - * other materials provided with the distribution. - * - * Neither the name of FIRST nor the names of its contributors may be used to endorse or - * promote products derived from this software without specific prior written permission. - * - * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS - * LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.firstinspires.ftc.robotcontroller.external.samples; - -import com.qualcomm.robotcore.eventloop.opmode.Disabled; -import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode; -import com.qualcomm.robotcore.eventloop.opmode.TeleOp; -import com.qualcomm.robotcore.util.Range; - -/* - * This OpMode illustrates how to use an external "hardware" class to modularize all the robot's sensors and actuators. - * This approach is very efficient because the same hardware class can be used by all of your teleop and autonomous OpModes - * without requiring many copy & paste operations. Once you have defined and tested the hardware class with one OpMode, - * it is instantly available to other OpModes. - * - * The real benefit of this approach is that as you tweak your robot hardware, you only need to make changes in ONE place (the Hardware Class). - * So, to be effective you should put as much or your hardware setup and access code as possible in the hardware class. - * Essentially anything you do with hardware in BOTH Teleop and Auto should likely go in the hardware class. - * - * The Hardware Class is created in a separate file, and then an "instance" of this class is created in each OpMode. - * In order for the class to do typical OpMode things (like send telemetry data) it must be passed a reference to the - * OpMode object when it's created, so it can access all core OpMode functions. This is illustrated below. - * - * In this concept sample, the hardware class file is called RobotHardware.java and it must accompany this sample OpMode. - * So, if you copy ConceptExternalHardwareClass.java into TeamCode (using Android Studio or OnBotJava) then RobotHardware.java - * must also be copied to the same location (maintaining its name). - * - * For comparison purposes, this sample and its accompanying hardware class duplicates the functionality of the - * RobotTelopPOV_Linear OpMode. It assumes three motors (left_drive, right_drive and arm) and two servos (left_hand and right_hand) - * - * View the RobotHardware.java class file for more details - * - * Use Android Studio to Copy this Class, and Paste it into your team's code folder with a new name. - * Remove or comment out the @Disabled line to add this OpMode to the Driver Station OpMode list - * - * In OnBot Java, add a new OpMode, select this sample, and select TeleOp. - * Also add another new file named RobotHardware.java, select the sample with that name, and select Not an OpMode. - */ - -@TeleOp(name="Concept: Robot Hardware Class", group="Robot") -@Disabled -public class ConceptExternalHardwareClass extends LinearOpMode { - - // Create a RobotHardware object to be used to access robot hardware. - // Prefix any hardware functions with "robot." to access this class. - RobotHardware robot = new RobotHardware(this); - - @Override - public void runOpMode() { - double drive = 0; - double turn = 0; - double arm = 0; - double handOffset = 0; - - // initialize all the hardware, using the hardware class. See how clean and simple this is? - robot.init(); - - // Send telemetry message to signify robot waiting; - // Wait for the game to start (driver presses START) - waitForStart(); - - // run until the end of the match (driver presses STOP) - while (opModeIsActive()) { - - // Run wheels in POV mode (note: The joystick goes negative when pushed forward, so negate it) - // In this mode the Left stick moves the robot fwd and back, the Right stick turns left and right. - // This way it's also easy to just drive straight, or just turn. - drive = -gamepad1.left_stick_y; - turn = gamepad1.right_stick_x; - - // Combine drive and turn for blended motion. Use RobotHardware class - robot.driveRobot(drive, turn); - - // Use gamepad left & right Bumpers to open and close the claw - // Use the SERVO constants defined in RobotHardware class. - // Each time around the loop, the servos will move by a small amount. - // Limit the total offset to half of the full travel range - if (gamepad1.right_bumper) - handOffset += robot.HAND_SPEED; - else if (gamepad1.left_bumper) - handOffset -= robot.HAND_SPEED; - handOffset = Range.clip(handOffset, -0.5, 0.5); - - // Move both servos to new position. Use RobotHardware class - robot.setHandPositions(handOffset); - - // Use gamepad buttons to move arm up (Y) and down (A) - // Use the MOTOR constants defined in RobotHardware class. - if (gamepad1.y) - arm = robot.ARM_UP_POWER; - else if (gamepad1.a) - arm = robot.ARM_DOWN_POWER; - else - arm = 0; - - robot.setArmPower(arm); - - // Send telemetry messages to explain controls and show robot status - telemetry.addData("Drive", "Left Stick"); - telemetry.addData("Turn", "Right Stick"); - telemetry.addData("Arm Up/Down", "Y & A Buttons"); - telemetry.addData("Hand Open/Closed", "Left and Right Bumpers"); - telemetry.addData("-", "-------"); - - telemetry.addData("Drive Power", "%.2f", drive); - telemetry.addData("Turn Power", "%.2f", turn); - telemetry.addData("Arm Power", "%.2f", arm); - telemetry.addData("Hand Position", "Offset = %.2f", handOffset); - telemetry.update(); - - // Pace this loop so hands move at a reasonable speed. - sleep(50); - } - } -} diff --git a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptGamepadRumble.java b/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptGamepadRumble.java deleted file mode 100644 index cf846e1..0000000 --- a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptGamepadRumble.java +++ /dev/null @@ -1,200 +0,0 @@ -package org.firstinspires.ftc.robotcontroller.external.samples; - -import com.qualcomm.robotcore.eventloop.opmode.Disabled; -import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode; -import com.qualcomm.robotcore.eventloop.opmode.TeleOp; -import com.qualcomm.robotcore.hardware.Gamepad; -import com.qualcomm.robotcore.util.ElapsedTime; - -/* - * This OpMode illustrates using the rumble feature of many gamepads. - * - * Note: Some gamepads "rumble" better than others. - * The Xbox & PS4 have a left (rumble1) and right (rumble2) rumble motor. - * These two gamepads have two distinct rumble modes: Large on the left, and small on the right - * The Etpark gamepad may only respond to rumble1, and may only run at full power. - * The Logitech F310 gamepad does not have *any* rumble ability. - * - * Moral: You should use this sample to experiment with your specific gamepads to explore their rumble features. - * - * The rumble motors are accessed through the standard gamepad1 and gamepad2 objects. - * Several new methods were added to the Gamepad class in FTC SDK Rev 7 - * The key methods are as follows: - * - * .rumble(double rumble1, double rumble2, int durationMs) - * This method sets the rumble power of both motors for a specific time duration. - * Both rumble arguments are motor-power levels in the 0.0 to 1.0 range. - * durationMs is the desired length of the rumble action in milliseconds. - * This method returns immediately. - * Note: - * Use a durationMs of Gamepad.RUMBLE_DURATION_CONTINUOUS to provide a continuous rumble - * Use a power of 0, or duration of 0 to stop a rumble. - * - * .rumbleBlips(int count) allows an easy way to signal the driver with a series of rumble blips. - * Just specify how many blips you want. - * This method returns immediately. - * - * .runRumbleEffect(customRumbleEffect) allows you to run a custom rumble sequence that you have - * built using the Gamepad.RumbleEffect.Builder() - * A "custom effect" is a sequence of steps, where each step can rumble any of the - * rumble motors for a specific period at a specific power level. - * The Custom Effect will play as the (un-blocked) OpMode continues to run - * - * .isRumbling() returns true if there is a rumble effect in progress. - * Use this call to prevent stepping on a current rumble. - * - * .stopRumble() Stop any ongoing rumble or custom rumble effect. - * - * .rumble(int durationMs) Full power rumble for fixed duration. - * - * Note: Whenever a new Rumble command is issued, any currently executing rumble action will - * be truncated, and the new action started immediately. Take these precautions: - * 1) Do Not SPAM the rumble motors by issuing rapid fire commands - * 2) Multiple sources for rumble commands must coordinate to avoid tromping on each other. - * - * This can be achieved several possible ways: - * 1) Only having one source for rumble actions - * 2) Issuing rumble commands on transitions, rather than states. - * e.g. The moment a touch sensor is pressed, rather than the entire time it is being pressed. - * 3) Scheduling rumble commands based on timed events. e.g. 10 seconds prior to endgame - * 4) Rumble on non-overlapping mechanical actions. e.g. arm fully-extended or fully-retracted. - * 5) Use isRumbling() to hold off on a new rumble if one is already in progress. - * - * The examples shown here are representstive of how to invoke a gamepad rumble. - * It is assumed that these will be modified to suit the specific robot and team strategy needs. - * - * ######## Read the telemetry display on the Driver Station Screen for instructions. ###### - * - * Ex 1) This example shows a) how to create a custom rumble effect, and then b) how to trigger it based - * on game time. One use for this might be to alert the driver that half-time or End-game is approaching. - * - * Ex 2) This example shows tying the rumble power to a changing sensor value. - * In this case it is the Gamepad trigger, but it could be any sensor output scaled to the 0-1 range. - * Since it takes over the rumble motors, it is only performed when the Left Bumper is pressed. - * Note that this approach MUST include a way to turn OFF the rumble when the button is released. - * - * Ex 3) This example shows a simple way to trigger a 3-blip sequence. In this case it is - * triggered by the gamepad A (Cross) button, but it could be any sensor, like a touch or light sensor. - * Note that this code ensures that it only rumbles once when the input goes true. - * - * Ex 4) This example shows how to trigger a single rumble when an input value gets over a certain value. - * In this case it is reading the Right Trigger, but it could be any variable sensor, like a - * range sensor, or position sensor. The code needs to ensure that it is only triggered once, so - * it waits till the sensor drops back below the threshold before it can trigger again. - * - * Use Android Studio to Copy this Class, and Paste it into your team's code folder with a new name. - * Remove or comment out the @Disabled line to add this OpMode to the Driver Station OpMode list. - */ - -@Disabled -@TeleOp(name="Concept: Gamepad Rumble", group ="Concept") -public class ConceptGamepadRumble extends LinearOpMode -{ - boolean lastA = false; // Use to track the prior button state. - boolean lastLB = false; // Use to track the prior button state. - boolean highLevel = false; // used to prevent multiple level-based rumbles. - boolean secondHalf = false; // Use to prevent multiple half-time warning rumbles. - - Gamepad.RumbleEffect customRumbleEffect; // Use to build a custom rumble sequence. - ElapsedTime runtime = new ElapsedTime(); // Use to determine when end game is starting. - - final double HALF_TIME = 60.0; // Wait this many seconds before rumble-alert for half-time. - final double TRIGGER_THRESHOLD = 0.75; // Squeeze more than 3/4 to get rumble. - - @Override - public void runOpMode() - { - // Example 1. a) start by creating a three-pulse rumble sequence: right, LEFT, LEFT - customRumbleEffect = new Gamepad.RumbleEffect.Builder() - .addStep(0.0, 1.0, 500) // Rumble right motor 100% for 500 mSec - .addStep(0.0, 0.0, 300) // Pause for 300 mSec - .addStep(1.0, 0.0, 250) // Rumble left motor 100% for 250 mSec - .addStep(0.0, 0.0, 250) // Pause for 250 mSec - .addStep(1.0, 0.0, 250) // Rumble left motor 100% for 250 mSec - .build(); - - telemetry.addData(">", "Press Start"); - telemetry.update(); - - waitForStart(); - runtime.reset(); // Start game timer. - - // Loop while monitoring buttons for rumble triggers - while (opModeIsActive()) - { - // Read and save the current gamepad button states. - boolean currentA = gamepad1.a ; - boolean currentLB = gamepad1.left_bumper ; - - // Display the current Rumble status. Just for interest. - telemetry.addData(">", "Are we RUMBLING? %s\n", gamepad1.isRumbling() ? "YES" : "no" ); - - // ---------------------------------------------------------------------------------------- - // Example 1. b) Watch the runtime timer, and run the custom rumble when we hit half-time. - // Make sure we only signal once by setting "secondHalf" flag to prevent further rumbles. - // ---------------------------------------------------------------------------------------- - if ((runtime.seconds() > HALF_TIME) && !secondHalf) { - gamepad1.runRumbleEffect(customRumbleEffect); - secondHalf =true; - } - - // Display the time remaining while we are still counting down. - if (!secondHalf) { - telemetry.addData(">", "Halftime Alert Countdown: %3.0f Sec \n", (HALF_TIME - runtime.seconds()) ); - } - - - // ---------------------------------------------------------------------------------------- - // Example 2. If Left Bumper is being pressed, power the rumble motors based on the two trigger depressions. - // This is useful to see how the rumble feels at various power levels. - // ---------------------------------------------------------------------------------------- - if (currentLB) { - // Left Bumper is being pressed, so send left and right "trigger" values to left and right rumble motors. - gamepad1.rumble(gamepad1.left_trigger, gamepad1.right_trigger, Gamepad.RUMBLE_DURATION_CONTINUOUS); - - // Show what is being sent to rumbles - telemetry.addData(">", "Squeeze triggers to control rumbles"); - telemetry.addData("> : Rumble", "Left: %.0f%% Right: %.0f%%", gamepad1.left_trigger * 100, gamepad1.right_trigger * 100); - } else { - // Make sure rumble is turned off when Left Bumper is released (only one time each press) - if (lastLB) { - gamepad1.stopRumble(); - } - - // Prompt for manual rumble action - telemetry.addData(">", "Hold Left-Bumper to test Manual Rumble"); - telemetry.addData(">", "Press A (Cross) for three blips"); - telemetry.addData(">", "Squeeze right trigger slowly for 1 blip"); - } - lastLB = currentLB; // remember the current button state for next time around the loop - - - // ---------------------------------------------------------------------------------------- - // Example 3. Blip 3 times at the moment that A (Cross) is pressed. (look for pressed transition) - // BUT !!! Skip it altogether if the Gamepad is already rumbling. - // ---------------------------------------------------------------------------------------- - if (currentA && !lastA) { - if (!gamepad1.isRumbling()) // Check for possible overlap of rumbles. - gamepad1.rumbleBlips(3); - } - lastA = currentA; // remember the current button state for next time around the loop - - - // ---------------------------------------------------------------------------------------- - // Example 4. Rumble once when gamepad right trigger goes above the THRESHOLD. - // ---------------------------------------------------------------------------------------- - if (gamepad1.right_trigger > TRIGGER_THRESHOLD) { - if (!highLevel) { - gamepad1.rumble(0.9, 0, 200); // 200 mSec burst on left motor. - highLevel = true; // Hold off any more triggers - } - } else { - highLevel = false; // We can trigger again now. - } - - // Send the telemetry data to the Driver Station, and then pause to pace the program. - telemetry.update(); - sleep(10); - } - } -} diff --git a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptGamepadTouchpad.java b/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptGamepadTouchpad.java deleted file mode 100644 index 84d8cec..0000000 --- a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptGamepadTouchpad.java +++ /dev/null @@ -1,77 +0,0 @@ -package org.firstinspires.ftc.robotcontroller.external.samples; - -import com.qualcomm.robotcore.eventloop.opmode.Disabled; -import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode; -import com.qualcomm.robotcore.eventloop.opmode.TeleOp; -import org.firstinspires.ftc.robotcore.external.Telemetry; - -/* - * This OpMode illustrates using the touchpad feature found on some gamepads. - * - * The Sony PS4 gamepad can detect two distinct touches on the central touchpad. - * Other gamepads with different touchpads may provide mixed results. - * - * The touchpads are accessed through the standard gamepad1 and gamepad2 objects. - * Several new members were added to the Gamepad class in FTC SDK Rev 7 - * - * .touchpad_finger_1 returns true if at least one finger is detected. - * .touchpad_finger_1_x finger 1 X coordinate. Valid if touchpad_finger_1 is true - * .touchpad_finger_1_y finger 1 Y coordinate. Valid if touchpad_finger_1 is true - * - * .touchpad_finger_2 returns true if a second finger is detected - * .touchpad_finger_2_x finger 2 X coordinate. Valid if touchpad_finger_2 is true - * .touchpad_finger_2_y finger 2 Y coordinate. Valid if touchpad_finger_2 is true - * - * Finger touches are reported with an X and Y coordinate in following coordinate system. - * - * 1) X is the Horizontal axis, and Y is the vertical axis - * 2) The 0,0 origin is at the center of the touchpad. - * 3) 1.0, 1.0 is at the top right corner of the touchpad. - * 4) -1.0,-1.0 is at the bottom left corner of the touchpad. - * - * Use Android Studio to Copy this Class, and Paste it into your team's code folder with a new name. - * Remove or comment out the @Disabled line to add this OpMode to the Driver Station OpMode list. - */ - -@Disabled -@TeleOp(name="Concept: Gamepad Touchpad", group ="Concept") -public class ConceptGamepadTouchpad extends LinearOpMode -{ - @Override - public void runOpMode() - { - telemetry.setDisplayFormat(Telemetry.DisplayFormat.MONOSPACE); - - telemetry.addData(">", "Press Start"); - telemetry.update(); - - waitForStart(); - - while (opModeIsActive()) - { - boolean finger = false; - - // Display finger 1 x & y position if finger detected - if(gamepad1.touchpad_finger_1) - { - finger = true; - telemetry.addLine(String.format("Finger 1: x=%5.2f y=%5.2f\n", gamepad1.touchpad_finger_1_x, gamepad1.touchpad_finger_1_y)); - } - - // Display finger 2 x & y position if finger detected - if(gamepad1.touchpad_finger_2) - { - finger = true; - telemetry.addLine(String.format("Finger 2: x=%5.2f y=%5.2f\n", gamepad1.touchpad_finger_2_x, gamepad1.touchpad_finger_2_y)); - } - - if(!finger) - { - telemetry.addLine("No fingers"); - } - - telemetry.update(); - sleep(10); - } - } -} diff --git a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptLEDStick.java b/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptLEDStick.java deleted file mode 100644 index 01729bb..0000000 --- a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptLEDStick.java +++ /dev/null @@ -1,123 +0,0 @@ -package org.firstinspires.ftc.robotcontroller.external.samples; -/* - Copyright (c) 2021-24 Alan Smith - - All rights reserved. - - Redistribution and use in source and binary forms, with or without modification, - are permitted (subject to the limitations in the disclaimer below) provided that - the following conditions are met: - - Redistributions of source code must retain the above copyright notice, this list - of conditions and the following disclaimer. - - Redistributions in binary form must reproduce the above copyright notice, this - list of conditions and the following disclaimer in the documentation and/or - other materials provided with the distribution. - - Neither the name of Alan Smith nor the names of its contributors may be used to - endorse or promote products derived from this software without specific prior - written permission. - - NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS - LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESSFOR A PARTICULAR PURPOSE - ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR - TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -import android.graphics.Color; - -import com.qualcomm.hardware.sparkfun.SparkFunLEDStick; -import com.qualcomm.robotcore.eventloop.opmode.Disabled; -import com.qualcomm.robotcore.eventloop.opmode.OpMode; -import com.qualcomm.robotcore.eventloop.opmode.TeleOp; -import com.qualcomm.robotcore.util.Range; - -/* - * This OpMode illustrates how to use the SparkFun QWIIC LED Strip - * - * This is a simple way to add a strip of 10 LEDs to your robot where you can set the color of each - * LED or the whole strip. This allows for driver feedback or even just fun ways to show your team - * colors. - * - * Why? - * Because more LEDs == more fun!! - * - * This OpMode assumes that the QWIIC LED Stick is attached to an I2C interface named "back_leds" in the robot configuration. - * - * Use Android Studio to Copy this Class, and Paste it into your team's code folder with a new name. - * Remove or comment out the @Disabled line to add this OpMode to the Driver Station OpMode list - * - * You can buy this product here: https://www.sparkfun.com/products/18354 - * Don't forget to also buy this to make it easy to connect to your Control or Expansion Hub: - * https://www.sparkfun.com/products/25596 - */ -@TeleOp(name = "Concept: LED Stick", group = "Concept") -@Disabled -public class ConceptLEDStick extends OpMode { - private boolean wasUp; - private boolean wasDown; - private int brightness = 5; // this needs to be between 0 and 31 - private final static double END_GAME_TIME = 120 - 30; - - private SparkFunLEDStick ledStick; - - @Override - public void init() { - ledStick = hardwareMap.get(SparkFunLEDStick.class, "back_leds"); - ledStick.setBrightness(brightness); - ledStick.setColor(Color.GREEN); - } - - @Override - public void start() { - resetRuntime(); - } - - @Override - public void loop() { - telemetry.addLine("Hold the A button to turn blue"); - telemetry.addLine("Hold the B button to turn red"); - telemetry.addLine("Hold the left bumper to turn off"); - telemetry.addLine("Use DPAD Up/Down to change brightness"); - - if (getRuntime() > END_GAME_TIME) { - int[] ledColors = {Color.RED, Color.YELLOW, Color.RED, Color.YELLOW, Color.RED, - Color.YELLOW, Color.RED, Color.YELLOW, Color.RED, Color.YELLOW}; - ledStick.setColors(ledColors); - } else if (gamepad1.a) { - ledStick.setColor(Color.BLUE); - } else if (gamepad1.b) { - ledStick.setColor(Color.RED); - } else if (gamepad1.left_bumper) { - ledStick.turnAllOff(); - } else { - ledStick.setColor(Color.GREEN); - } - - /* - * Use DPAD up and down to change brightness - */ - int newBrightness = brightness; - if (gamepad1.dpad_up && !wasUp) { - newBrightness = brightness + 1; - } else if (gamepad1.dpad_down && !wasDown) { - newBrightness = brightness - 1; - } - if (newBrightness != brightness) { - brightness = Range.clip(newBrightness, 0, 31); - ledStick.setBrightness(brightness); - } - telemetry.addData("Brightness", brightness); - - wasDown = gamepad1.dpad_down; - wasUp = gamepad1.dpad_up; - } -} diff --git a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptMotorBulkRead.java b/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptMotorBulkRead.java deleted file mode 100644 index 5439f78..0000000 --- a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptMotorBulkRead.java +++ /dev/null @@ -1,227 +0,0 @@ -/* Copyright (c) 2019 Phil Malone. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted (subject to the limitations in the disclaimer below) provided that - * the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list - * of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this - * list of conditions and the following disclaimer in the documentation and/or - * other materials provided with the distribution. - * - * Neither the name of FIRST nor the names of its contributors may be used to endorse or - * promote products derived from this software without specific prior written permission. - * - * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS - * LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.firstinspires.ftc.robotcontroller.external.samples; - -import com.qualcomm.hardware.lynx.LynxModule; -import com.qualcomm.robotcore.eventloop.opmode.Disabled; -import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode; -import com.qualcomm.robotcore.eventloop.opmode.TeleOp; -import com.qualcomm.robotcore.hardware.DcMotorEx; -import com.qualcomm.robotcore.util.ElapsedTime; - -import java.util.List; - -/* - * This OpMode illustrates how to use the Expansion Hub's Bulk-Read feature to speed up control cycle times. - * In this example there are 4 motors that need their encoder positions, and velocities read. - * The sample is written to work with one or two expansion hubs, with no assumption as to where the motors are located. - * - * Three scenarios are tested: - * Cache Mode = OFF This is the normal default, where no cache is used, and every read produces a discrete transaction with - * an expansion hub, which is the slowest approach, but guarentees that the value is as fresh (recent) as possible.. - * - * Cache Mode = AUTO This mode will attempt to minimize the number of discrete read commands, by performing bulk-reads - * and then returning values that have been cached. The cache is updated automatically whenever any specific encoder is re-read. - * This mode will always return new data, but it may perform more bulk-reads than absolutely required. - * Extra reads will be performed if multiple encoder/velocity reads are performed on the same encoder in one control cycle. - * This mode is a good compromise between the OFF and MANUAL modes. - * Note: If there are significant user-program delays between encoder reads, the cached value may not be fresh (recent). - * You can issue a clearBulkCache() call at any time force a fresh bulk-read on the next encoder read. - * - * Cache Mode = MANUAL This mode requires the user's code to determine the best time to clear the cached bulk-read data. - * Well organized code will reset the cache once at the beginning of the control cycle, and then immediately read and store all the encoder values. - * This approach will produce the shortest cycle times, but it does require the user to manually clear the cache. - * Since NO automatic Bulk-Reads are performed, neglecting to clear the bulk cache will result in the same values being returned - * each time an encoder read is performed. - * - * ------------------------------------- - * - * General tip to speed up your control cycles: - * - * No matter what method you use to read encoders and other inputs, you should try to - * avoid reading the same encoder input multiple times around a control loop. - * Under normal conditions, this will slow down the control loop. - * The preferred method is to read all the required inputs ONCE at the beginning of the loop, - * and save the values in variable that can be used by other parts of the control code. - * - * eg: if you are sending encoder positions to your telemetry display, putting a getCurrentPosition() - * call in the telemetry statement will force the code to go and get another copy which will take time. - * It's much better read the position into a variable once, and use that variable for control AND display. - * Reading saved variables takes no time at all. - * - * Once you put all your sensor reads at the beginning of the control cycle, it's very easy to use - * the bulk-read AUTO mode to streamline your cycle timing. - */ -@TeleOp (name = "Motor Bulk Reads", group = "Tests") -@Disabled -public class ConceptMotorBulkRead extends LinearOpMode { - - final int TEST_CYCLES = 500; // Number of control cycles to run to determine cycle times. - - private DcMotorEx m1, m2, m3, m4; // Motor Objects - private long e1, e2, e3, e4; // Encoder Values - private double v1, v2, v3, v4; // Velocities - - // Cycle Times - double t1 = 0; - double t2 = 0; - double t3 = 0; - - @Override - public void runOpMode() { - - int cycles; - - // Important Step 1: Make sure you use DcMotorEx when you instantiate your motors. - m1 = hardwareMap.get(DcMotorEx.class, "m1"); // Configure the robot to use these 4 motor names, - m2 = hardwareMap.get(DcMotorEx.class, "m2"); // or change these strings to match your existing Robot Configuration. - m3 = hardwareMap.get(DcMotorEx.class, "m3"); - m4 = hardwareMap.get(DcMotorEx.class, "m4"); - - // Important Step 2: Get access to a list of Expansion Hub Modules to enable changing caching methods. - List allHubs = hardwareMap.getAll(LynxModule.class); - - ElapsedTime timer = new ElapsedTime(); - - telemetry.addData(">", "Press START to start tests"); - telemetry.addData(">", "Test results will update for each access method."); - telemetry.update(); - waitForStart(); - - // -------------------------------------------------------------------------------------- - // Run control loop using legacy encoder reads - // In this mode, a single read is done for each encoder position, and a bulk read is done for each velocity read. - // This is the worst case scenario. - // This is the same as using LynxModule.BulkCachingMode.OFF - // -------------------------------------------------------------------------------------- - - displayCycleTimes("Test 1 of 3 (Wait for completion)"); - - timer.reset(); - cycles = 0; - while (opModeIsActive() && (cycles++ < TEST_CYCLES)) { - e1 = m1.getCurrentPosition(); - e2 = m2.getCurrentPosition(); - e3 = m3.getCurrentPosition(); - e4 = m4.getCurrentPosition(); - - v1 = m1.getVelocity(); - v2 = m2.getVelocity(); - v3 = m3.getVelocity(); - v4 = m4.getVelocity(); - - // Put Control loop action code here. - - } - // calculate the average cycle time. - t1 = timer.milliseconds() / cycles; - displayCycleTimes("Test 2 of 3 (Wait for completion)"); - - // -------------------------------------------------------------------------------------- - // Run test cycles using AUTO cache mode - // In this mode, only one bulk read is done per cycle, UNLESS you read a specific encoder/velocity item AGAIN in that cycle. - // -------------------------------------------------------------------------------------- - - // Important Step 3: Option A. Set all Expansion hubs to use the AUTO Bulk Caching mode - for (LynxModule module : allHubs) { - module.setBulkCachingMode(LynxModule.BulkCachingMode.AUTO); - } - - timer.reset(); - cycles = 0; - while (opModeIsActive() && (cycles++ < TEST_CYCLES)) { - e1 = m1.getCurrentPosition(); // Uses 1 bulk-read for all 4 encoder/velocity reads, - e2 = m2.getCurrentPosition(); // but don't do any `get` operations more than once per cycle. - e3 = m3.getCurrentPosition(); - e4 = m4.getCurrentPosition(); - - v1 = m1.getVelocity(); - v2 = m2.getVelocity(); - v3 = m3.getVelocity(); - v4 = m4.getVelocity(); - - // Put Control loop action code here. - - } - // calculate the average cycle time. - t2 = timer.milliseconds() / cycles; - displayCycleTimes("Test 3 of 3 (Wait for completion)"); - - // -------------------------------------------------------------------------------------- - // Run test cycles using MANUAL cache mode - // In this mode, only one block read is done each control cycle. - // This is the MOST efficient method, but it does require that the cache is cleared manually each control cycle. - // -------------------------------------------------------------------------------------- - - // Important Step 3: Option B. Set all Expansion hubs to use the MANUAL Bulk Caching mode - for (LynxModule module : allHubs) { - module.setBulkCachingMode(LynxModule.BulkCachingMode.MANUAL); - } - - timer.reset(); - cycles = 0; - while (opModeIsActive() && (cycles++ < TEST_CYCLES)) { - - // Important Step 4: If you are using MANUAL mode, you must clear the BulkCache once per control cycle - for (LynxModule module : allHubs) { - module.clearBulkCache(); - } - - e1 = m1.getCurrentPosition(); // Uses 1 bulk-read to obtain ALL the motor data - e2 = m2.getCurrentPosition(); // There is no penalty for doing more `get` operations in this cycle, - e3 = m3.getCurrentPosition(); // but they will return the same data. - e4 = m4.getCurrentPosition(); - - v1 = m1.getVelocity(); - v2 = m2.getVelocity(); - v3 = m3.getVelocity(); - v4 = m4.getVelocity(); - - // Put Control loop action code here. - - } - // calculate the average cycle time. - t3 = timer.milliseconds() / cycles; - displayCycleTimes("Complete"); - - // wait until op-mode is stopped by user, before clearing display. - while (opModeIsActive()) ; - } - - // Display three comparison times. - void displayCycleTimes(String status) { - telemetry.addData("Testing", status); - telemetry.addData("Cache = OFF", "%5.1f mS/cycle", t1); - telemetry.addData("Cache = AUTO", "%5.1f mS/cycle", t2); - telemetry.addData("Cache = MANUAL", "%5.1f mS/cycle", t3); - telemetry.update(); - } -} - diff --git a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptNullOp.java b/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptNullOp.java deleted file mode 100644 index 4a887bd..0000000 --- a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptNullOp.java +++ /dev/null @@ -1,89 +0,0 @@ -/* Copyright (c) 2017 FIRST. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted (subject to the limitations in the disclaimer below) provided that - * the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list - * of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this - * list of conditions and the following disclaimer in the documentation and/or - * other materials provided with the distribution. - * - * Neither the name of FIRST nor the names of its contributors may be used to endorse or - * promote products derived from this software without specific prior written permission. - * - * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS - * LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.firstinspires.ftc.robotcontroller.external.samples; - -import com.qualcomm.robotcore.eventloop.opmode.Disabled; -import com.qualcomm.robotcore.eventloop.opmode.OpMode; -import com.qualcomm.robotcore.eventloop.opmode.TeleOp; -import com.qualcomm.robotcore.util.ElapsedTime; - -/* - * Demonstrates an empty iterative OpMode - */ -@TeleOp(name = "Concept: NullOp", group = "Concept") -@Disabled -public class ConceptNullOp extends OpMode { - - private ElapsedTime runtime = new ElapsedTime(); - - /** - * This method will be called once, when the INIT button is pressed. - */ - @Override - public void init() { - telemetry.addData("Status", "Initialized"); - } - - /** - * This method will be called repeatedly during the period between when - * the INIT button is pressed and when the START button is pressed (or the - * OpMode is stopped). - */ - @Override - public void init_loop() { - } - - /** - * This method will be called once, when the START button is pressed. - */ - @Override - public void start() { - runtime.reset(); - } - - /** - * This method will be called repeatedly during the period between when - * the START button is pressed and when the OpMode is stopped. - */ - @Override - public void loop() { - telemetry.addData("Status", "Run Time: " + runtime.toString()); - } - - /** - * This method will be called once, when this OpMode is stopped. - *

- * Your ability to control hardware from this method will be limited. - */ - @Override - public void stop() { - - } -} diff --git a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptRampMotorSpeed.java b/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptRampMotorSpeed.java deleted file mode 100644 index 6e0be37..0000000 --- a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptRampMotorSpeed.java +++ /dev/null @@ -1,114 +0,0 @@ -/* Copyright (c) 2017 FIRST. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted (subject to the limitations in the disclaimer below) provided that - * the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list - * of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this - * list of conditions and the following disclaimer in the documentation and/or - * other materials provided with the distribution. - * - * Neither the name of FIRST nor the names of its contributors may be used to endorse or - * promote products derived from this software without specific prior written permission. - * - * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS - * LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.firstinspires.ftc.robotcontroller.external.samples; - -import com.qualcomm.robotcore.eventloop.opmode.Disabled; -import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode; -import com.qualcomm.robotcore.eventloop.opmode.TeleOp; -import com.qualcomm.robotcore.hardware.DcMotor; - -/* - * This OpMode ramps a single motor speed up and down repeatedly until Stop is pressed. - * The code is structured as a LinearOpMode - * - * This code assumes a DC motor configured with the name "left_drive" as is found on a Robot. - * - * INCREMENT sets how much to increase/decrease the power each cycle - * CYCLE_MS sets the update period. - * - * Use Android Studio to Copy this Class, and Paste it into your team's code folder with a new name. - * Remove or comment out the @Disabled line to add this OpMode to the Driver Station OpMode list - */ -@TeleOp(name = "Concept: Ramp Motor Speed", group = "Concept") -@Disabled -public class ConceptRampMotorSpeed extends LinearOpMode { - - static final double INCREMENT = 0.01; // amount to ramp motor each CYCLE_MS cycle - static final int CYCLE_MS = 50; // period of each cycle - static final double MAX_FWD = 1.0; // Maximum FWD power applied to motor - static final double MAX_REV = -1.0; // Maximum REV power applied to motor - - // Define class members - DcMotor motor; - double power = 0; - boolean rampUp = true; - - - @Override - public void runOpMode() { - - // Connect to motor (Assume standard left wheel) - // Change the text in quotes to match any motor name on your robot. - motor = hardwareMap.get(DcMotor.class, "left_drive"); - - // Wait for the start button - telemetry.addData(">", "Press Start to run Motors." ); - telemetry.update(); - waitForStart(); - - // Ramp motor speeds till stop pressed. - while(opModeIsActive()) { - - // Ramp the motors, according to the rampUp variable. - if (rampUp) { - // Keep stepping up until we hit the max value. - power += INCREMENT ; - if (power >= MAX_FWD ) { - power = MAX_FWD; - rampUp = !rampUp; // Switch ramp direction - } - } - else { - // Keep stepping down until we hit the min value. - power -= INCREMENT ; - if (power <= MAX_REV ) { - power = MAX_REV; - rampUp = !rampUp; // Switch ramp direction - } - } - - // Display the current value - telemetry.addData("Motor Power", "%5.2f", power); - telemetry.addData(">", "Press Stop to end test." ); - telemetry.update(); - - // Set the motor to the new power and pause; - motor.setPower(power); - sleep(CYCLE_MS); - idle(); - } - - // Turn off motor and signal done; - motor.setPower(0); - telemetry.addData(">", "Done"); - telemetry.update(); - - } -} diff --git a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptRevLED.java b/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptRevLED.java deleted file mode 100644 index 9c168d5..0000000 --- a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptRevLED.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - Copyright (c) 2024 Alan Smith - All rights reserved. - Redistribution and use in source and binary forms, with or without modification, - are permitted (subject to the limitations in the disclaimer below) provided that - the following conditions are met: - Redistributions of source code must retain the above copyright notice, this list - of conditions and the following disclaimer. - Redistributions in binary form must reproduce the above copyright notice, this - list of conditions and the following disclaimer in the documentation and/or - other materials provided with the distribution. - Neither the name of Alan Smith nor the names of its contributors may be used to - endorse or promote products derived from this software without specific prior - written permission. - NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS - LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESSFOR A PARTICULAR PURPOSE - ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR - TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ -package org.firstinspires.ftc.robotcontroller.external.samples; - -/* - * This OpMode illustrates how to use the REV Digital Indicator - * - * This is a simple way to add the REV Digital Indicator which has a red and green LED. - * (and as you may remember, red + green = yellow so when they are both on you can get yellow) - * - * Why? - * You can use this to show information to the driver. For example, green might be that you can - * pick up more game elements and red would be that you already have the possession limit. - * - * This OpMode assumes that the REV Digital Indicator is setup as 2 Digital Channels named - * front_led_green and front_led_red. (the green should be the lower of the 2 channels it is plugged - * into and the red should be the higher) - * - * Use Android Studio to Copy this Class, and Paste it into your team's code folder with a new name. - * Remove or comment out the @Disabled line to add this OpMode to the Driver Station OpMode list - * - * You can buy this product here: https://www.revrobotics.com/rev-31-2010/ - */ -import com.qualcomm.robotcore.eventloop.opmode.Disabled; -import com.qualcomm.robotcore.eventloop.opmode.OpMode; -import com.qualcomm.robotcore.eventloop.opmode.TeleOp; -import com.qualcomm.robotcore.hardware.LED; - -@TeleOp(name = "Concept: RevLED", group = "Concept") -@Disabled -public class ConceptRevLED extends OpMode { - LED frontLED_red; - LED frontLED_green; - - @Override - public void init() { - frontLED_green = hardwareMap.get(LED.class, "front_led_green"); - frontLED_red = hardwareMap.get(LED.class, "front_led_red"); - } - - @Override - public void loop() { - if (gamepad1.a) { - frontLED_red.on(); - } else { - frontLED_red.off(); - } - if (gamepad1.b) { - frontLED_green.on(); - } else { - frontLED_green.off(); - } - } -} diff --git a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptRevSPARKMini.java b/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptRevSPARKMini.java deleted file mode 100644 index 798d689..0000000 --- a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptRevSPARKMini.java +++ /dev/null @@ -1,111 +0,0 @@ -/* Copyright (c) 2018 FIRST. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted (subject to the limitations in the disclaimer below) provided that - * the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list - * of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this - * list of conditions and the following disclaimer in the documentation and/or - * other materials provided with the distribution. - * - * Neither the name of FIRST nor the names of its contributors may be used to endorse or - * promote products derived from this software without specific prior written permission. - * - * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS - * LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.firstinspires.ftc.robotcontroller.external.samples; - -import com.qualcomm.robotcore.eventloop.opmode.Disabled; -import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode; -import com.qualcomm.robotcore.eventloop.opmode.TeleOp; -import com.qualcomm.robotcore.hardware.DcMotorSimple; -import com.qualcomm.robotcore.util.ElapsedTime; -import com.qualcomm.robotcore.util.Range; - - -/* - * This OpMode demonstrates a POV Drive system, with commented-out code for a Tank Drive system, - * for a two wheeled robot using two REV SPARKminis. - * To use this example, connect two REV SPARKminis into servo ports on the Control Hub. On the - * robot configuration, use the drop down list under 'Servos' to select 'REV SPARKmini Controller' - * and name them 'left_drive' and 'right_drive'. - * - * Use Android Studio to Copy this Class, and Paste it into your team's code folder with a new name. - * Remove or comment out the @Disabled line to add this OpMode to the Driver Station OpMode list - */ - -@TeleOp(name="REV SPARKmini Simple Drive Example", group="Concept") -@Disabled -public class ConceptRevSPARKMini extends LinearOpMode { - - // Declare OpMode members. - private ElapsedTime runtime = new ElapsedTime(); - private DcMotorSimple leftDrive = null; - private DcMotorSimple rightDrive = null; - - @Override - public void runOpMode() { - telemetry.addData("Status", "Initialized"); - telemetry.update(); - - // Initialize the hardware variables. Note that the strings used here as parameters - // to 'get' must correspond to the names assigned during robot configuration. - leftDrive = hardwareMap.get(DcMotorSimple.class, "left_drive"); - rightDrive = hardwareMap.get(DcMotorSimple.class, "right_drive"); - - // Most robots need the motor on one side to be reversed to drive forward - // Reverse the motor that runs backward when connected directly to the battery - leftDrive.setDirection(DcMotorSimple.Direction.FORWARD); - rightDrive.setDirection(DcMotorSimple.Direction.REVERSE); - - // Wait for the game to start (driver presses START) - waitForStart(); - runtime.reset(); - - // run until the end of the match (driver presses STOP) - while (opModeIsActive()) { - - // Setup a variable for each drive wheel to save power level for telemetry - double leftPower; - double rightPower; - - // Choose to drive using either Tank Mode, or POV Mode - // Comment out the method that's not used. The default below is POV. - - // POV Mode uses left stick to go forward, and right stick to turn. - // - This uses basic math to combine motions and is easier to drive straight. - double drive = -gamepad1.left_stick_y; - double turn = gamepad1.right_stick_x; - leftPower = Range.clip(drive + turn, -1.0, 1.0) ; - rightPower = Range.clip(drive - turn, -1.0, 1.0) ; - - // Tank Mode uses one stick to control each wheel. - // - This requires no math, but it is hard to drive forward slowly and keep straight. - // leftPower = -gamepad1.left_stick_y ; - // rightPower = -gamepad1.right_stick_y ; - - // Send calculated power to wheels - leftDrive.setPower(leftPower); - rightDrive.setPower(rightPower); - - // Show the elapsed game time and wheel power. - telemetry.addData("Status", "Run Time: " + runtime.toString()); - telemetry.addData("Motors", "left (%.2f), right (%.2f)", leftPower, rightPower); - telemetry.update(); - } - } -} diff --git a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptScanServo.java b/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptScanServo.java deleted file mode 100644 index 2b8ad33..0000000 --- a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptScanServo.java +++ /dev/null @@ -1,115 +0,0 @@ -/* Copyright (c) 2017 FIRST. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted (subject to the limitations in the disclaimer below) provided that - * the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list - * of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this - * list of conditions and the following disclaimer in the documentation and/or - * other materials provided with the distribution. - * - * Neither the name of FIRST nor the names of its contributors may be used to endorse or - * promote products derived from this software without specific prior written permission. - * - * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS - * LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.firstinspires.ftc.robotcontroller.external.samples; - -import com.qualcomm.robotcore.eventloop.opmode.Disabled; -import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode; -import com.qualcomm.robotcore.eventloop.opmode.TeleOp; -import com.qualcomm.robotcore.hardware.Servo; - -/* - * This OpMode scans a single servo back and forward until Stop is pressed. - * The code is structured as a LinearOpMode - * INCREMENT sets how much to increase/decrease the servo position each cycle - * CYCLE_MS sets the update period. - * - * This code assumes a Servo configured with the name "left_hand" as is found on a Robot. - * - * NOTE: When any servo position is set, ALL attached servos are activated, so ensure that any other - * connected servos are able to move freely before running this test. - * - * Use Android Studio to Copy this Class, and Paste it into your team's code folder with a new name. - * Remove or comment out the @Disabled line to add this OpMode to the Driver Station OpMode list - */ -@TeleOp(name = "Concept: Scan Servo", group = "Concept") -@Disabled -public class ConceptScanServo extends LinearOpMode { - - static final double INCREMENT = 0.01; // amount to slew servo each CYCLE_MS cycle - static final int CYCLE_MS = 50; // period of each cycle - static final double MAX_POS = 1.0; // Maximum rotational position - static final double MIN_POS = 0.0; // Minimum rotational position - - // Define class members - Servo servo; - double position = (MAX_POS - MIN_POS) / 2; // Start at halfway position - boolean rampUp = true; - - - @Override - public void runOpMode() { - - // Connect to servo (Assume Robot Left Hand) - // Change the text in quotes to match any servo name on your robot. - servo = hardwareMap.get(Servo.class, "left_hand"); - - // Wait for the start button - telemetry.addData(">", "Press Start to scan Servo." ); - telemetry.update(); - waitForStart(); - - - // Scan servo till stop pressed. - while(opModeIsActive()){ - - // slew the servo, according to the rampUp (direction) variable. - if (rampUp) { - // Keep stepping up until we hit the max value. - position += INCREMENT ; - if (position >= MAX_POS ) { - position = MAX_POS; - rampUp = !rampUp; // Switch ramp direction - } - } - else { - // Keep stepping down until we hit the min value. - position -= INCREMENT ; - if (position <= MIN_POS ) { - position = MIN_POS; - rampUp = !rampUp; // Switch ramp direction - } - } - - // Display the current value - telemetry.addData("Servo Position", "%5.2f", position); - telemetry.addData(">", "Press Stop to end test." ); - telemetry.update(); - - // Set the servo to the new position and pause; - servo.setPosition(position); - sleep(CYCLE_MS); - idle(); - } - - // Signal done; - telemetry.addData(">", "Done"); - telemetry.update(); - } -} diff --git a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptSoundsASJava.java b/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptSoundsASJava.java deleted file mode 100644 index 1ceaa17..0000000 --- a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptSoundsASJava.java +++ /dev/null @@ -1,133 +0,0 @@ -/* Copyright (c) 2018 FIRST. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted (subject to the limitations in the disclaimer below) provided that - * the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list - * of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this - * list of conditions and the following disclaimer in the documentation and/or - * other materials provided with the distribution. - * - * Neither the name of FIRST nor the names of its contributors may be used to endorse or - * promote products derived from this software without specific prior written permission. - * - * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS - * LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.firstinspires.ftc.robotcontroller.external.samples; - -import com.qualcomm.ftccommon.SoundPlayer; -import com.qualcomm.robotcore.eventloop.opmode.Disabled; -import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode; -import com.qualcomm.robotcore.eventloop.opmode.TeleOp; - -/* - * This OpMode demonstrates how to play simple sounds on both the RC and DS phones. - * It illustrates how to build sounds into your application as a resource. - * This technique is best suited for use with Android Studio since it assumes you will be creating a new application - * - * If you are using OnBotJava, please see the ConceptSoundsOnBotJava sample - * - * Use Android Studio to Copy this Class, and Paste it into your team's code folder with a new name. - * Remove or comment out the @Disabled line to add this OpMode to the Driver Station OpMode list - * - * Operation: - * - * Gamepad X & B buttons are used to trigger sounds in this example, but any event can be used. - * Note: Time should be allowed for sounds to complete before playing other sounds. - * - * For sound files to be used as a compiled-in resource, they need to be located in a folder called "raw" under your "res" (resources) folder. - * You can create your own "raw" folder from scratch, or you can copy the one from the FtcRobotController module. - * - * Android Studio coders will ultimately need a folder in your path as follows: - * /TeamCode/src/main/res/raw - * - * Copy any .wav files you want to play into this folder. - * Make sure that your files ONLY use lower-case characters, and have no spaces or special characters other than underscore. - * - * The name you give your .wav files will become the resource ID for these sounds. - * eg: gold.wav becomes R.raw.gold - * - * If you wish to use the sounds provided for this sample, they are located in: - * /FtcRobotController/src/main/res/raw - * You can copy and paste the entire 'raw' folder using Android Studio. - * - */ - -@TeleOp(name="Concept: Sound Resources", group="Concept") -@Disabled -public class ConceptSoundsASJava extends LinearOpMode { - - // Declare OpMode members. - private boolean goldFound; // Sound file present flags - private boolean silverFound; - - private boolean isX = false; // Gamepad button state variables - private boolean isB = false; - - private boolean wasX = false; // Gamepad button history variables - private boolean WasB = false; - - @Override - public void runOpMode() { - - // Determine Resource IDs for sounds built into the RC application. - int silverSoundID = hardwareMap.appContext.getResources().getIdentifier("silver", "raw", hardwareMap.appContext.getPackageName()); - int goldSoundID = hardwareMap.appContext.getResources().getIdentifier("gold", "raw", hardwareMap.appContext.getPackageName()); - - // Determine if sound resources are found. - // Note: Preloading is NOT required, but it's a good way to verify all your sounds are available before you run. - if (goldSoundID != 0) - goldFound = SoundPlayer.getInstance().preload(hardwareMap.appContext, goldSoundID); - - if (silverSoundID != 0) - silverFound = SoundPlayer.getInstance().preload(hardwareMap.appContext, silverSoundID); - - // Display sound status - telemetry.addData("gold resource", goldFound ? "Found" : "NOT found\n Add gold.wav to /src/main/res/raw" ); - telemetry.addData("silver resource", silverFound ? "Found" : "Not found\n Add silver.wav to /src/main/res/raw" ); - - // Wait for the game to start (driver presses START) - telemetry.addData(">", "Press Start to continue"); - telemetry.update(); - waitForStart(); - - telemetry.addData(">", "Press X, B to play sounds."); - telemetry.update(); - - // run until the end of the match (driver presses STOP) - while (opModeIsActive()) { - - // say Silver each time gamepad X is pressed (This sound is a resource) - if (silverFound && (isX = gamepad1.x) && !wasX) { - SoundPlayer.getInstance().startPlaying(hardwareMap.appContext, silverSoundID); - telemetry.addData("Playing", "Resource Silver"); - telemetry.update(); - } - - // say Gold each time gamepad B is pressed (This sound is a resource) - if (goldFound && (isB = gamepad1.b) && !WasB) { - SoundPlayer.getInstance().startPlaying(hardwareMap.appContext, goldSoundID); - telemetry.addData("Playing", "Resource Gold"); - telemetry.update(); - } - - // Save last button states - wasX = isX; - WasB = isB; - } - } -} diff --git a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptSoundsOnBotJava.java b/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptSoundsOnBotJava.java deleted file mode 100644 index fbb7416..0000000 --- a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptSoundsOnBotJava.java +++ /dev/null @@ -1,120 +0,0 @@ -/* Copyright (c) 2018 FIRST. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted (subject to the limitations in the disclaimer below) provided that - * the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list - * of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this - * list of conditions and the following disclaimer in the documentation and/or - * other materials provided with the distribution. - * - * Neither the name of FIRST nor the names of its contributors may be used to endorse or - * promote products derived from this software without specific prior written permission. - * - * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS - * LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.firstinspires.ftc.robotcontroller.external.samples; - -import com.qualcomm.ftccommon.SoundPlayer; -import com.qualcomm.robotcore.eventloop.opmode.Disabled; -import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode; -import com.qualcomm.robotcore.eventloop.opmode.TeleOp; - -import java.io.File; - -/* - * This OpMode demonstrates how to play simple sounds on both the RC and DS phones. - * It illustrates how to play sound files that have been copied to the RC Phone - * This technique is best suited for use with OnBotJava since it does not require the app to be modified. - * - * Operation: - * - * Gamepad X & B buttons are used to trigger sounds in this example, but any event can be used. - * Note: Time should be allowed for sounds to complete before playing other sounds. - * - * To play a new sound, you will need to copy the .wav files to the phone, and then provide the full path to them as part of your OpMode. - * This is done in this sample for the two sound files. silver.wav and gold.wav - * - * You can put the files in a variety of soundPaths, but we recommend you put them in the /FIRST/blocks/sounds folder. - * Your OpModes will have guaranteed access to this folder, and you can transfer files into this folder using the BLOCKS web page. - * -- There is a link called "sounds" on the right hand side of the color bar on the BLOCKS page that can be used to send sound files to this folder by default. - * Or you can use Windows File Manager, or ADB to transfer the sound files - * - * To get full use of THIS sample, you will need to copy two sound file called silver.wav and gold.wav to /FIRST/blocks/sounds on the RC phone. - * They can be located here: - * https://github.com/ftctechnh/ftc_app/tree/master/FtcRobotController/src/main/res/raw/gold.wav - * https://github.com/ftctechnh/ftc_app/tree/master/FtcRobotController/src/main/res/raw/silver.wav - */ - -@TeleOp(name="Concept: Sound Files", group="Concept") -@Disabled -public class ConceptSoundsOnBotJava extends LinearOpMode { - - // Point to sound files on the phone's drive - private String soundPath = "/FIRST/blocks/sounds"; - private File goldFile = new File("/sdcard" + soundPath + "/gold.wav"); - private File silverFile = new File("/sdcard" + soundPath + "/silver.wav"); - - // Declare OpMode members. - private boolean isX = false; // Gamepad button state variables - private boolean isB = false; - - private boolean wasX = false; // Gamepad button history variables - private boolean WasB = false; - - @Override - public void runOpMode() { - - // Make sure that the sound files exist on the phone - boolean goldFound = goldFile.exists(); - boolean silverFound = silverFile.exists(); - - // Display sound status - telemetry.addData("gold sound", goldFound ? "Found" : "NOT Found \nCopy gold.wav to " + soundPath ); - telemetry.addData("silver sound", silverFound ? "Found" : "NOT Found \nCopy silver.wav to " + soundPath ); - - // Wait for the game to start (driver presses PLAY) - telemetry.addData(">", "Press Start to continue"); - telemetry.update(); - waitForStart(); - - telemetry.addData(">", "Press X or B to play sounds."); - telemetry.update(); - - // run until the end of the match (driver presses STOP) - while (opModeIsActive()) { - - // say Silver each time gamepad X is pressed (This sound is a resource) - if (silverFound && (isX = gamepad1.x) && !wasX) { - SoundPlayer.getInstance().startPlaying(hardwareMap.appContext, silverFile); - telemetry.addData("Playing", "Silver File"); - telemetry.update(); - } - - // say Gold each time gamepad B is pressed (This sound is a resource) - if (goldFound && (isB = gamepad1.b) && !WasB) { - SoundPlayer.getInstance().startPlaying(hardwareMap.appContext, goldFile); - telemetry.addData("Playing", "Gold File"); - telemetry.update(); - } - - // Save last button states - wasX = isX; - WasB = isB; - } - } -} diff --git a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptSoundsSKYSTONE.java b/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptSoundsSKYSTONE.java deleted file mode 100644 index 983e434..0000000 --- a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptSoundsSKYSTONE.java +++ /dev/null @@ -1,122 +0,0 @@ -/* Copyright (c) 2018 FIRST. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted (subject to the limitations in the disclaimer below) provided that - * the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list - * of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this - * list of conditions and the following disclaimer in the documentation and/or - * other materials provided with the distribution. - * - * Neither the name of FIRST nor the names of its contributors may be used to endorse or - * promote products derived from this software without specific prior written permission. - * - * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS - * LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.firstinspires.ftc.robotcontroller.external.samples; - -import android.content.Context; -import com.qualcomm.ftccommon.SoundPlayer; -import com.qualcomm.robotcore.eventloop.opmode.Disabled; -import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode; -import com.qualcomm.robotcore.eventloop.opmode.TeleOp; - -/* - * This OpMode demonstrates how to play one of the several SKYSTONE/Star Wars sounds loaded into the SDK. - * It does this by creating a simple "chooser" controlled by the gamepad Up Down buttons. - * This code also prevents sounds from stacking up by setting a "playing" flag, which is cleared when the sound finishes playing. - * - * Use Android Studio to Copy this Class, and Paste it into your team's code folder with a new name. - * Remove or comment out the @Disabled line to add this OpMode to the Driver Station OpMode list - * - * Operation: - * Use the DPAD to change the selected sound, and the Right Bumper to play it. - */ - -@TeleOp(name="SKYSTONE Sounds", group="Concept") -@Disabled -public class ConceptSoundsSKYSTONE extends LinearOpMode { - - // List of available sound resources - String sounds[] = {"ss_alarm", "ss_bb8_down", "ss_bb8_up", "ss_darth_vader", "ss_fly_by", - "ss_mf_fail", "ss_laser", "ss_laser_burst", "ss_light_saber", "ss_light_saber_long", "ss_light_saber_short", - "ss_light_speed", "ss_mine", "ss_power_up", "ss_r2d2_up", "ss_roger_roger", "ss_siren", "ss_wookie" }; - boolean soundPlaying = false; - - @Override - public void runOpMode() { - - // Variables for choosing from the available sounds - int soundIndex = 0; - int soundID = -1; - boolean was_dpad_up = false; - boolean was_dpad_down = false; - - Context myApp = hardwareMap.appContext; - - // create a sound parameter that holds the desired player parameters. - SoundPlayer.PlaySoundParams params = new SoundPlayer.PlaySoundParams(); - params.loopControl = 0; - params.waitForNonLoopingSoundsToFinish = true; - - // In this sample, we will skip waiting for the user to press play, and start displaying sound choices right away - while (!isStopRequested()) { - - // Look for DPAD presses to change the selection - if (gamepad1.dpad_down && !was_dpad_down) { - // Go to next sound (with list wrap) and display it - soundIndex = (soundIndex + 1) % sounds.length; - } - - if (gamepad1.dpad_up && !was_dpad_up) { - // Go to previous sound (with list wrap) and display it - soundIndex = (soundIndex + sounds.length - 1) % sounds.length; - } - - // Look for trigger to see if we should play sound - // Only start a new sound if we are currently not playing one. - if (gamepad1.right_bumper && !soundPlaying) { - - // Determine Resource IDs for the sounds you want to play, and make sure it's valid. - if ((soundID = myApp.getResources().getIdentifier(sounds[soundIndex], "raw", myApp.getPackageName())) != 0){ - - // Signal that the sound is now playing. - soundPlaying = true; - - // Start playing, and also Create a callback that will clear the playing flag when the sound is complete. - SoundPlayer.getInstance().startPlaying(myApp, soundID, params, null, - new Runnable() { - public void run() { - soundPlaying = false; - }} ); - } - } - - // Remember the last state of the dpad to detect changes. - was_dpad_up = gamepad1.dpad_up; - was_dpad_down = gamepad1.dpad_down; - - // Display the current sound choice, and the playing status. - telemetry.addData("", "Use DPAD up/down to choose sound."); - telemetry.addData("", "Press Right Bumper to play sound."); - telemetry.addData("", ""); - telemetry.addData("Sound >", sounds[soundIndex]); - telemetry.addData("Status >", soundPlaying ? "Playing" : "Stopped"); - telemetry.update(); - } - } -} diff --git a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptTelemetry.java b/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptTelemetry.java deleted file mode 100644 index f2c6097..0000000 --- a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptTelemetry.java +++ /dev/null @@ -1,177 +0,0 @@ -/* Copyright (c) 2017 FIRST. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted (subject to the limitations in the disclaimer below) provided that - * the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list - * of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this - * list of conditions and the following disclaimer in the documentation and/or - * other materials provided with the distribution. - * - * Neither the name of FIRST nor the names of its contributors may be used to endorse or - * promote products derived from this software without specific prior written permission. - * - * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS - * LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.firstinspires.ftc.robotcontroller.external.samples; - -import com.qualcomm.robotcore.eventloop.opmode.Disabled; -import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode; -import com.qualcomm.robotcore.eventloop.opmode.TeleOp; -import com.qualcomm.robotcore.hardware.VoltageSensor; -import com.qualcomm.robotcore.util.ElapsedTime; -import org.firstinspires.ftc.robotcore.external.Func; -import org.firstinspires.ftc.robotcore.external.Telemetry; - -/* - * This OpMode illustrates various ways in which telemetry can be - * transmitted from the robot controller to the driver station. The sample illustrates - * numeric and text data, formatted output, and optimized evaluation of expensive-to-acquire - * information. The telemetry log is illustrated by scrolling a poem - * to the driver station. - * - * Also see the Telemetry javadocs. - */ -@TeleOp(name = "Concept: Telemetry", group = "Concept") -@Disabled -public class ConceptTelemetry extends LinearOpMode { - /** Keeps track of the line of the poem which is to be emitted next */ - int poemLine = 0; - - /** Keeps track of how long it's been since we last emitted a line of poetry */ - ElapsedTime poemElapsed = new ElapsedTime(); - - static final String[] poem = new String[] { - - "Mary had a little lamb,", - "His fleece was white as snow,", - "And everywhere that Mary went,", - "The lamb was sure to go.", - "", - "He followed her to school one day,", - "Which was against the rule,", - "It made the children laugh and play", - "To see a lamb at school.", - "", - "And so the teacher turned it out,", - "But still it lingered near,", - "And waited patiently about,", - "Till Mary did appear.", - "", - "\"Why does the lamb love Mary so?\"", - "The eager children cry.", - "\"Why, Mary loves the lamb, you know,\"", - "The teacher did reply.", - "", - "" - }; - - @Override public void runOpMode() { - - /* we keep track of how long it's been since the OpMode was started, just - * to have some interesting data to show */ - ElapsedTime opmodeRunTime = new ElapsedTime(); - - // We show the log in oldest-to-newest order, as that's better for poetry - telemetry.log().setDisplayOrder(Telemetry.Log.DisplayOrder.OLDEST_FIRST); - // We can control the number of lines shown in the log - telemetry.log().setCapacity(6); - // The interval between lines of poetry, in seconds - double sPoemInterval = 0.6; - - /* - * Wait until we've been given the ok to go. For something to do, we emit the - * elapsed time as we sit here and wait. If we didn't want to do anything while - * we waited, we would just call waitForStart(). - */ - while (!isStarted()) { - telemetry.addData("time", "%.1f seconds", opmodeRunTime.seconds()); - telemetry.update(); - idle(); - } - - // Ok, we've been given the ok to go - - /* - * As an illustration, the first line on our telemetry display will display the battery voltage. - * The idea here is that it's expensive to compute the voltage (at least for purposes of illustration) - * so you don't want to do it unless the data is _actually_ going to make it to the - * driver station (recall that telemetry transmission is throttled to reduce bandwidth use. - * Note that getBatteryVoltage() below returns 'Infinity' if there's no voltage sensor attached. - * - * @see Telemetry#getMsTransmissionInterval() - */ - telemetry.addData("voltage", "%.1f volts", new Func() { - @Override public Double value() { - return getBatteryVoltage(); - } - }); - - // Reset to keep some timing stats for the post-'start' part of the OpMode - opmodeRunTime.reset(); - int loopCount = 1; - - // Go go gadget robot! - while (opModeIsActive()) { - - // Emit poetry if it's been a while - if (poemElapsed.seconds() > sPoemInterval) { - emitPoemLine(); - } - - // As an illustration, show some loop timing information - telemetry.addData("loop count", loopCount); - telemetry.addData("ms/loop", "%.3f ms", opmodeRunTime.milliseconds() / loopCount); - - // Show joystick information as some other illustrative data - telemetry.addLine("left joystick | ") - .addData("x", gamepad1.left_stick_x) - .addData("y", gamepad1.left_stick_y); - telemetry.addLine("right joystick | ") - .addData("x", gamepad1.right_stick_x) - .addData("y", gamepad1.right_stick_y); - - /* - * Transmit the telemetry to the driver station, subject to throttling. - * See the documentation for Telemetry.getMsTransmissionInterval() for more information. - */ - telemetry.update(); - - // Update loop info - loopCount++; - } - } - - // emits a line of poetry to the telemetry log - void emitPoemLine() { - telemetry.log().add(poem[poemLine]); - poemLine = (poemLine+1) % poem.length; - poemElapsed.reset(); - } - - // Computes the current battery voltage - double getBatteryVoltage() { - double result = Double.POSITIVE_INFINITY; - for (VoltageSensor sensor : hardwareMap.voltageSensor) { - double voltage = sensor.getVoltage(); - if (voltage > 0) { - result = Math.min(result, voltage); - } - } - return result; - } -} diff --git a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptVisionColorLocator.java b/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptVisionColorLocator.java deleted file mode 100644 index 987694d..0000000 --- a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptVisionColorLocator.java +++ /dev/null @@ -1,191 +0,0 @@ -/* - * Copyright (c) 2024 Phil Malone - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.firstinspires.ftc.robotcontroller.external.samples; - -import android.util.Size; - -import com.qualcomm.robotcore.eventloop.opmode.Disabled; -import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode; -import com.qualcomm.robotcore.eventloop.opmode.TeleOp; -import com.qualcomm.robotcore.util.SortOrder; - -import org.firstinspires.ftc.robotcore.external.Telemetry; -import org.firstinspires.ftc.robotcore.external.hardware.camera.WebcamName; -import org.firstinspires.ftc.vision.VisionPortal; -import org.firstinspires.ftc.vision.opencv.ColorBlobLocatorProcessor; -import org.firstinspires.ftc.vision.opencv.ColorRange; -import org.firstinspires.ftc.vision.opencv.ImageRegion; -import org.opencv.core.RotatedRect; - -import java.util.List; - -/* - * This OpMode illustrates how to use a video source (camera) to locate specifically colored regions - * - * Unlike a "color sensor" which determines the color of an object in the field of view, this "color locator" - * will search the Region Of Interest (ROI) in a camera image, and find any "blobs" of color that match the requested color range. - * These blobs can be further filtered and sorted to find the one most likely to be the item the user is looking for. - * - * To perform this function, a VisionPortal runs a ColorBlobLocatorProcessor process. - * The ColorBlobLocatorProcessor process is created first, and then the VisionPortal is built to use this process. - * The ColorBlobLocatorProcessor analyses the ROI and locates pixels that match the ColorRange to form a "mask". - * The matching pixels are then collected into contiguous "blobs" of pixels. The outer boundaries of these blobs are called its "contour". - * For each blob, the process then creates the smallest possible rectangle "boxFit" that will fully encase the contour. - * The user can then call getBlobs() to retrieve the list of Blobs, where each Blob contains the contour and the boxFit data. - * Note: The default sort order for Blobs is ContourArea, in descending order, so the biggest contours are listed first. - * - * To aid the user, a colored boxFit rectangle is drawn on the camera preview to show the location of each Blob - * The original Blob contour can also be added to the preview. This is helpful when configuring the ColorBlobLocatorProcessor parameters. - * - * Use Android Studio to Copy this Class, and Paste it into your team's code folder with a new name. - * Remove or comment out the @Disabled line to add this OpMode to the Driver Station OpMode list - */ - -@Disabled -@TeleOp(name = "Concept: Vision Color-Locator", group = "Concept") -public class ConceptVisionColorLocator extends LinearOpMode -{ - @Override - public void runOpMode() - { - /* Build a "Color Locator" vision processor based on the ColorBlobLocatorProcessor class. - * - Specify the color range you are looking for. You can use a predefined color, or create you own color range - * .setTargetColorRange(ColorRange.BLUE) // use a predefined color match - * Available predefined colors are: RED, BLUE YELLOW GREEN - * .setTargetColorRange(new ColorRange(ColorSpace.YCrCb, // or define your own color match - * new Scalar( 32, 176, 0), - * new Scalar(255, 255, 132))) - * - * - Focus the color locator by defining a RegionOfInterest (ROI) which you want to search. - * This can be the entire frame, or a sub-region defined using: - * 1) standard image coordinates or 2) a normalized +/- 1.0 coordinate system. - * Use one form of the ImageRegion class to define the ROI. - * ImageRegion.entireFrame() - * ImageRegion.asImageCoordinates(50, 50, 150, 150) 100x100 pixel square near the upper left corner - * ImageRegion.asUnityCenterCoordinates(-0.5, 0.5, 0.5, -0.5) 50% width/height square centered on screen - * - * - Define which contours are included. - * You can get ALL the contours, or you can skip any contours that are completely inside another contour. - * .setContourMode(ColorBlobLocatorProcessor.ContourMode.ALL_FLATTENED_HIERARCHY) // return all contours - * .setContourMode(ColorBlobLocatorProcessor.ContourMode.EXTERNAL_ONLY) // exclude contours inside other contours - * note: EXTERNAL_ONLY helps to avoid bright reflection spots from breaking up areas of solid color. - * - * - turn the display of contours ON or OFF. Turning this on helps debugging but takes up valuable CPU time. - * .setDrawContours(true) - * - * - include any pre-processing of the image or mask before looking for Blobs. - * There are some extra processing you can include to improve the formation of blobs. Using these features requires - * an understanding of how they may effect the final blobs. The "pixels" argument sets the NxN kernel size. - * .setBlurSize(int pixels) Blurring an image helps to provide a smooth color transition between objects, and smoother contours. - * The higher the number of pixels, the more blurred the image becomes. - * Note: Even "pixels" values will be incremented to satisfy the "odd number" requirement. - * Blurring too much may hide smaller features. A "pixels" size of 5 is good for a 320x240 image. - * .setErodeSize(int pixels) Erosion removes floating pixels and thin lines so that only substantive objects remain. - * Erosion can grow holes inside regions, and also shrink objects. - * "pixels" in the range of 2-4 are suitable for low res images. - * .setDilateSize(int pixels) Dilation makes objects more visible by filling in small holes, making lines appear thicker, - * and making filled shapes appear larger. Dilation is useful for joining broken parts of an - * object, such as when removing noise from an image. - * "pixels" in the range of 2-4 are suitable for low res images. - */ - ColorBlobLocatorProcessor colorLocator = new ColorBlobLocatorProcessor.Builder() - .setTargetColorRange(ColorRange.BLUE) // use a predefined color match - .setContourMode(ColorBlobLocatorProcessor.ContourMode.EXTERNAL_ONLY) // exclude blobs inside blobs - .setRoi(ImageRegion.asUnityCenterCoordinates(-0.5, 0.5, 0.5, -0.5)) // search central 1/4 of camera view - .setDrawContours(true) // Show contours on the Stream Preview - .setBlurSize(5) // Smooth the transitions between different colors in image - .build(); - - /* - * Build a vision portal to run the Color Locator process. - * - * - Add the colorLocator process created above. - * - Set the desired video resolution. - * Since a high resolution will not improve this process, choose a lower resolution that is - * supported by your camera. This will improve overall performance and reduce latency. - * - Choose your video source. This may be - * .setCamera(hardwareMap.get(WebcamName.class, "Webcam 1")) ..... for a webcam - * or - * .setCamera(BuiltinCameraDirection.BACK) ... for a Phone Camera - */ - VisionPortal portal = new VisionPortal.Builder() - .addProcessor(colorLocator) - .setCameraResolution(new Size(320, 240)) - .setCamera(hardwareMap.get(WebcamName.class, "Webcam 1")) - .build(); - - telemetry.setMsTransmissionInterval(50); // Speed up telemetry updates, Just use for debugging. - telemetry.setDisplayFormat(Telemetry.DisplayFormat.MONOSPACE); - - // WARNING: To be able to view the stream preview on the Driver Station, this code runs in INIT mode. - while (opModeIsActive() || opModeInInit()) - { - telemetry.addData("preview on/off", "... Camera Stream\n"); - - // Read the current list - List blobs = colorLocator.getBlobs(); - - /* - * The list of Blobs can be filtered to remove unwanted Blobs. - * Note: All contours will be still displayed on the Stream Preview, but only those that satisfy the filter - * conditions will remain in the current list of "blobs". Multiple filters may be used. - * - * Use any of the following filters. - * - * ColorBlobLocatorProcessor.Util.filterByArea(minArea, maxArea, blobs); - * A Blob's area is the number of pixels contained within the Contour. Filter out any that are too big or small. - * Start with a large range and then refine the range based on the likely size of the desired object in the viewfinder. - * - * ColorBlobLocatorProcessor.Util.filterByDensity(minDensity, maxDensity, blobs); - * A blob's density is an indication of how "full" the contour is. - * If you put a rubber band around the contour you would get the "Convex Hull" of the contour. - * The density is the ratio of Contour-area to Convex Hull-area. - * - * ColorBlobLocatorProcessor.Util.filterByAspectRatio(minAspect, maxAspect, blobs); - * A blob's Aspect ratio is the ratio of boxFit long side to short side. - * A perfect Square has an aspect ratio of 1. All others are > 1 - */ - ColorBlobLocatorProcessor.Util.filterByArea(50, 20000, blobs); // filter out very small blobs. - - /* - * The list of Blobs can be sorted using the same Blob attributes as listed above. - * No more than one sort call should be made. Sorting can use ascending or descending order. - * ColorBlobLocatorProcessor.Util.sortByArea(SortOrder.DESCENDING, blobs); // Default - * ColorBlobLocatorProcessor.Util.sortByDensity(SortOrder.DESCENDING, blobs); - * ColorBlobLocatorProcessor.Util.sortByAspectRatio(SortOrder.DESCENDING, blobs); - */ - - telemetry.addLine(" Area Density Aspect Center"); - - // Display the size (area) and center location for each Blob. - for(ColorBlobLocatorProcessor.Blob b : blobs) - { - RotatedRect boxFit = b.getBoxFit(); - telemetry.addLine(String.format("%5d %4.2f %5.2f (%3d,%3d)", - b.getContourArea(), b.getDensity(), b.getAspectRatio(), (int) boxFit.center.x, (int) boxFit.center.y)); - } - - telemetry.update(); - sleep(50); - } - } -} diff --git a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptVisionColorSensor.java b/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptVisionColorSensor.java deleted file mode 100644 index 6be2bc4..0000000 --- a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptVisionColorSensor.java +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Copyright (c) 2024 Phil Malone - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.firstinspires.ftc.robotcontroller.external.samples; - -import android.graphics.Color; -import android.util.Size; - -import com.qualcomm.robotcore.eventloop.opmode.Disabled; -import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode; -import com.qualcomm.robotcore.eventloop.opmode.TeleOp; - -import org.firstinspires.ftc.robotcore.external.hardware.camera.WebcamName; -import org.firstinspires.ftc.vision.VisionPortal; -import org.firstinspires.ftc.vision.opencv.ImageRegion; -import org.firstinspires.ftc.vision.opencv.PredominantColorProcessor; - -/* - * This OpMode illustrates how to use a video source (camera) as a color sensor - * - * A "color sensor" will typically determine the color of the object that it is pointed at. - * - * This sample performs the same function, except it uses a video camera to inspect an object or scene. - * The user may choose to inspect all, or just a Region of Interest (ROI), of the active camera view. - * The user must also provide a list of "acceptable colors" (Swatches) from which the closest matching color will be selected. - * - * To perform this function, a VisionPortal runs a PredominantColorProcessor process. - * The PredominantColorProcessor process is created first, and then the VisionPortal is built to use this process. - * The PredominantColorProcessor analyses the ROI and splits the colored pixels into several color-clusters. - * The largest of these clusters is then considered to be the "Predominant Color" - * The process then matches the Predominant Color with the closest Swatch and returns that match. - * - * To aid the user, a colored rectangle is drawn on the camera preview to show the RegionOfInterest, - * The Predominant Color is used to paint the rectangle border, so the user can verify that the color is reasonable. - * - * Use Android Studio to Copy this Class, and Paste it into your team's code folder with a new name. - * Remove or comment out the @Disabled line to add this OpMode to the Driver Station OpMode list - */ - -@Disabled -@TeleOp(name = "Concept: Vision Color-Sensor", group = "Concept") -public class ConceptVisionColorSensor extends LinearOpMode -{ - @Override - public void runOpMode() - { - /* Build a "Color Sensor" vision processor based on the PredominantColorProcessor class. - * - * - Focus the color sensor by defining a RegionOfInterest (ROI) which you want to inspect. - * This can be the entire frame, or a sub-region defined using: - * 1) standard image coordinates or 2) a normalized +/- 1.0 coordinate system. - * Use one form of the ImageRegion class to define the ROI. - * ImageRegion.entireFrame() - * ImageRegion.asImageCoordinates(50, 50, 150, 150) 100x100 pixel square near the upper left corner - * ImageRegion.asUnityCenterCoordinates(-0.1, 0.1, 0.1, -0.1) 10% width/height square centered on screen - * - * - Set the list of "acceptable" color swatches (matches). - * Only colors that you assign here will be returned. - * If you know the sensor will be pointing to one of a few specific colors, enter them here. - * Or, if the sensor may be pointed randomly, provide some additional colors that may match the surrounding. - * Possible choices are: - * RED, ORANGE, YELLOW, GREEN, CYAN, BLUE, PURPLE, MAGENTA, BLACK, WHITE; - * - * Note that in the example shown below, only some of the available colors are included. - * This will force any other colored region into one of these colors. - * eg: Green may be reported as YELLOW, as this may be the "closest" match. - */ - PredominantColorProcessor colorSensor = new PredominantColorProcessor.Builder() - .setRoi(ImageRegion.asUnityCenterCoordinates(-0.1, 0.1, 0.1, -0.1)) - .setSwatches( - PredominantColorProcessor.Swatch.RED, - PredominantColorProcessor.Swatch.BLUE, - PredominantColorProcessor.Swatch.YELLOW, - PredominantColorProcessor.Swatch.BLACK, - PredominantColorProcessor.Swatch.WHITE) - .build(); - - /* - * Build a vision portal to run the Color Sensor process. - * - * - Add the colorSensor process created above. - * - Set the desired video resolution. - * Since a high resolution will not improve this process, choose a lower resolution that is - * supported by your camera. This will improve overall performance and reduce latency. - * - Choose your video source. This may be - * .setCamera(hardwareMap.get(WebcamName.class, "Webcam 1")) ..... for a webcam - * or - * .setCamera(BuiltinCameraDirection.BACK) ... for a Phone Camera - */ - VisionPortal portal = new VisionPortal.Builder() - .addProcessor(colorSensor) - .setCameraResolution(new Size(320, 240)) - .setCamera(hardwareMap.get(WebcamName.class, "Webcam 1")) - .build(); - - telemetry.setMsTransmissionInterval(50); // Speed up telemetry updates, Just use for debugging. - - // WARNING: To be able to view the stream preview on the Driver Station, this code runs in INIT mode. - while (opModeIsActive() || opModeInInit()) - { - telemetry.addData("DS preview on/off", "3 dots, Camera Stream\n"); - - // Request the most recent color analysis. - // This will return the closest matching colorSwatch and the predominant RGB color. - // Note: to take actions based on the detected color, simply use the colorSwatch in a comparison or switch. - // eg: - // if (result.closestSwatch == PredominantColorProcessor.Swatch.RED) {... some code ...} - PredominantColorProcessor.Result result = colorSensor.getAnalysis(); - - // Display the Color Sensor result. - telemetry.addData("Best Match:", result.closestSwatch); - telemetry.addLine(String.format("R %3d, G %3d, B %3d", Color.red(result.rgb), Color.green(result.rgb), Color.blue(result.rgb))); - telemetry.update(); - - sleep(20); - } - } -} diff --git a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/RobotAutoDriveByEncoder_Linear.java b/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/RobotAutoDriveByEncoder_Linear.java deleted file mode 100644 index 63293d0..0000000 --- a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/RobotAutoDriveByEncoder_Linear.java +++ /dev/null @@ -1,187 +0,0 @@ -/* Copyright (c) 2017 FIRST. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted (subject to the limitations in the disclaimer below) provided that - * the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list - * of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this - * list of conditions and the following disclaimer in the documentation and/or - * other materials provided with the distribution. - * - * Neither the name of FIRST nor the names of its contributors may be used to endorse or - * promote products derived from this software without specific prior written permission. - * - * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS - * LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.firstinspires.ftc.robotcontroller.external.samples; - -import com.qualcomm.robotcore.eventloop.opmode.Autonomous; -import com.qualcomm.robotcore.eventloop.opmode.Disabled; -import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode; -import com.qualcomm.robotcore.hardware.DcMotor; -import com.qualcomm.robotcore.util.ElapsedTime; - -/* - * This OpMode illustrates the concept of driving a path based on encoder counts. - * The code is structured as a LinearOpMode - * - * The code REQUIRES that you DO have encoders on the wheels, - * otherwise you would use: RobotAutoDriveByTime; - * - * This code ALSO requires that the drive Motors have been configured such that a positive - * power command moves them forward, and causes the encoders to count UP. - * - * The desired path in this example is: - * - Drive forward for 48 inches - * - Spin right for 12 Inches - * - Drive Backward for 24 inches - * - Stop and close the claw. - * - * The code is written using a method called: encoderDrive(speed, leftInches, rightInches, timeoutS) - * that performs the actual movement. - * This method assumes that each movement is relative to the last stopping place. - * There are other ways to perform encoder based moves, but this method is probably the simplest. - * This code uses the RUN_TO_POSITION mode to enable the Motor controllers to generate the run profile - * - * Use Android Studio to Copy this Class, and Paste it into your team's code folder with a new name. - * Remove or comment out the @Disabled line to add this OpMode to the Driver Station OpMode list - */ - -@Autonomous(name="Robot: Auto Drive By Encoder", group="Robot") -@Disabled -public class RobotAutoDriveByEncoder_Linear extends LinearOpMode { - - /* Declare OpMode members. */ - private DcMotor leftDrive = null; - private DcMotor rightDrive = null; - - private ElapsedTime runtime = new ElapsedTime(); - - // Calculate the COUNTS_PER_INCH for your specific drive train. - // Go to your motor vendor website to determine your motor's COUNTS_PER_MOTOR_REV - // For external drive gearing, set DRIVE_GEAR_REDUCTION as needed. - // For example, use a value of 2.0 for a 12-tooth spur gear driving a 24-tooth spur gear. - // This is gearing DOWN for less speed and more torque. - // For gearing UP, use a gear ratio less than 1.0. Note this will affect the direction of wheel rotation. - static final double COUNTS_PER_MOTOR_REV = 1440 ; // eg: TETRIX Motor Encoder - static final double DRIVE_GEAR_REDUCTION = 1.0 ; // No External Gearing. - static final double WHEEL_DIAMETER_INCHES = 4.0 ; // For figuring circumference - static final double COUNTS_PER_INCH = (COUNTS_PER_MOTOR_REV * DRIVE_GEAR_REDUCTION) / - (WHEEL_DIAMETER_INCHES * 3.1415); - static final double DRIVE_SPEED = 0.6; - static final double TURN_SPEED = 0.5; - - @Override - public void runOpMode() { - - // Initialize the drive system variables. - leftDrive = hardwareMap.get(DcMotor.class, "left_drive"); - rightDrive = hardwareMap.get(DcMotor.class, "right_drive"); - - // To drive forward, most robots need the motor on one side to be reversed, because the axles point in opposite directions. - // When run, this OpMode should start both motors driving forward. So adjust these two lines based on your first test drive. - // Note: The settings here assume direct drive on left and right wheels. Gear Reduction or 90 Deg drives may require direction flips - leftDrive.setDirection(DcMotor.Direction.REVERSE); - rightDrive.setDirection(DcMotor.Direction.FORWARD); - - leftDrive.setMode(DcMotor.RunMode.STOP_AND_RESET_ENCODER); - rightDrive.setMode(DcMotor.RunMode.STOP_AND_RESET_ENCODER); - - leftDrive.setMode(DcMotor.RunMode.RUN_USING_ENCODER); - rightDrive.setMode(DcMotor.RunMode.RUN_USING_ENCODER); - - // Send telemetry message to indicate successful Encoder reset - telemetry.addData("Starting at", "%7d :%7d", - leftDrive.getCurrentPosition(), - rightDrive.getCurrentPosition()); - telemetry.update(); - - // Wait for the game to start (driver presses START) - waitForStart(); - - // Step through each leg of the path, - // Note: Reverse movement is obtained by setting a negative distance (not speed) - encoderDrive(DRIVE_SPEED, 48, 48, 5.0); // S1: Forward 47 Inches with 5 Sec timeout - encoderDrive(TURN_SPEED, 12, -12, 4.0); // S2: Turn Right 12 Inches with 4 Sec timeout - encoderDrive(DRIVE_SPEED, -24, -24, 4.0); // S3: Reverse 24 Inches with 4 Sec timeout - - telemetry.addData("Path", "Complete"); - telemetry.update(); - sleep(1000); // pause to display final telemetry message. - } - - /* - * Method to perform a relative move, based on encoder counts. - * Encoders are not reset as the move is based on the current position. - * Move will stop if any of three conditions occur: - * 1) Move gets to the desired position - * 2) Move runs out of time - * 3) Driver stops the OpMode running. - */ - public void encoderDrive(double speed, - double leftInches, double rightInches, - double timeoutS) { - int newLeftTarget; - int newRightTarget; - - // Ensure that the OpMode is still active - if (opModeIsActive()) { - - // Determine new target position, and pass to motor controller - newLeftTarget = leftDrive.getCurrentPosition() + (int)(leftInches * COUNTS_PER_INCH); - newRightTarget = rightDrive.getCurrentPosition() + (int)(rightInches * COUNTS_PER_INCH); - leftDrive.setTargetPosition(newLeftTarget); - rightDrive.setTargetPosition(newRightTarget); - - // Turn On RUN_TO_POSITION - leftDrive.setMode(DcMotor.RunMode.RUN_TO_POSITION); - rightDrive.setMode(DcMotor.RunMode.RUN_TO_POSITION); - - // reset the timeout time and start motion. - runtime.reset(); - leftDrive.setPower(Math.abs(speed)); - rightDrive.setPower(Math.abs(speed)); - - // keep looping while we are still active, and there is time left, and both motors are running. - // Note: We use (isBusy() && isBusy()) in the loop test, which means that when EITHER motor hits - // its target position, the motion will stop. This is "safer" in the event that the robot will - // always end the motion as soon as possible. - // However, if you require that BOTH motors have finished their moves before the robot continues - // onto the next step, use (isBusy() || isBusy()) in the loop test. - while (opModeIsActive() && - (runtime.seconds() < timeoutS) && - (leftDrive.isBusy() && rightDrive.isBusy())) { - - // Display it for the driver. - telemetry.addData("Running to", " %7d :%7d", newLeftTarget, newRightTarget); - telemetry.addData("Currently at", " at %7d :%7d", - leftDrive.getCurrentPosition(), rightDrive.getCurrentPosition()); - telemetry.update(); - } - - // Stop all motion; - leftDrive.setPower(0); - rightDrive.setPower(0); - - // Turn off RUN_TO_POSITION - leftDrive.setMode(DcMotor.RunMode.RUN_USING_ENCODER); - rightDrive.setMode(DcMotor.RunMode.RUN_USING_ENCODER); - - sleep(250); // optional pause after each move. - } - } -} diff --git a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/RobotAutoDriveByGyro_Linear.java b/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/RobotAutoDriveByGyro_Linear.java deleted file mode 100644 index ab70934..0000000 --- a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/RobotAutoDriveByGyro_Linear.java +++ /dev/null @@ -1,429 +0,0 @@ -/* Copyright (c) 2022 FIRST. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted (subject to the limitations in the disclaimer below) provided that - * the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list - * of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this - * list of conditions and the following disclaimer in the documentation and/or - * other materials provided with the distribution. - * - * Neither the name of FIRST nor the names of its contributors may be used to endorse or - * promote products derived from this software without specific prior written permission. - * - * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS - * LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.firstinspires.ftc.robotcontroller.external.samples; - -import com.qualcomm.hardware.rev.RevHubOrientationOnRobot; -import com.qualcomm.robotcore.eventloop.opmode.Autonomous; -import com.qualcomm.robotcore.eventloop.opmode.Disabled; -import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode; -import com.qualcomm.robotcore.hardware.DcMotor; -import com.qualcomm.robotcore.hardware.IMU; -import com.qualcomm.robotcore.util.ElapsedTime; -import com.qualcomm.robotcore.util.Range; -import org.firstinspires.ftc.robotcore.external.navigation.AngleUnit; -import org.firstinspires.ftc.robotcore.external.navigation.YawPitchRollAngles; - -/* - * This OpMode illustrates the concept of driving an autonomous path based on Gyro (IMU) heading and encoder counts. - * The code is structured as a LinearOpMode - * - * The path to be followed by the robot is built from a series of drive, turn or pause steps. - * Each step on the path is defined by a single function call, and these can be strung together in any order. - * - * The code REQUIRES that you have encoders on the drive motors, otherwise you should use: RobotAutoDriveByTime; - * - * This code uses the Universal IMU interface so it will work with either the BNO055, or BHI260 IMU. - * To run as written, the Control/Expansion hub should be mounted horizontally on a flat part of the robot chassis. - * The REV Logo should be facing UP, and the USB port should be facing forward. - * If this is not the configuration of your REV Control Hub, then the code should be modified to reflect the correct orientation. - * - * This sample requires that the drive Motors have been configured with names : left_drive and right_drive. - * It also requires that a positive power command moves both motors forward, and causes the encoders to count UP. - * So please verify that both of your motors move the robot forward on the first move. If not, make the required correction. - * See the beginning of runOpMode() to set the FORWARD/REVERSE option for each motor. - * - * This code uses RUN_TO_POSITION mode for driving straight, and RUN_USING_ENCODER mode for turning and holding. - * Note: This code implements the requirement of calling setTargetPosition() at least once before switching to RUN_TO_POSITION mode. - * - * Notes: - * - * All angles are referenced to the coordinate-frame that is set whenever resetHeading() is called. - * In this sample, the heading is reset when the Start button is touched on the Driver Station. - * Note: It would be possible to reset the heading after each move, but this would accumulate steering errors. - * - * The angle of movement/rotation is assumed to be a standardized rotation around the robot Z axis, - * which means that a Positive rotation is Counter Clockwise, looking down on the field. - * This is consistent with the FTC field coordinate conventions set out in the document: - * https://ftc-docs.firstinspires.org/field-coordinate-system - * - * Control Approach. - * - * To reach, or maintain a required heading, this code implements a basic Proportional Controller where: - * - * Steering power = Heading Error * Proportional Gain. - * - * "Heading Error" is calculated by taking the difference between the desired heading and the actual heading, - * and then "normalizing" it by converting it to a value in the +/- 180 degree range. - * - * "Proportional Gain" is a constant that YOU choose to set the "strength" of the steering response. - * - * Use Android Studio to Copy this Class, and Paste it into your "TeamCode" folder with a new name. - * Remove or comment out the @Disabled line to add this OpMode to the Driver Station OpMode list - */ - -@Autonomous(name="Robot: Auto Drive By Gyro", group="Robot") -@Disabled -public class RobotAutoDriveByGyro_Linear extends LinearOpMode { - - /* Declare OpMode members. */ - private DcMotor leftDrive = null; - private DcMotor rightDrive = null; - private IMU imu = null; // Control/Expansion Hub IMU - - private double headingError = 0; - - // These variable are declared here (as class members) so they can be updated in various methods, - // but still be displayed by sendTelemetry() - private double targetHeading = 0; - private double driveSpeed = 0; - private double turnSpeed = 0; - private double leftSpeed = 0; - private double rightSpeed = 0; - private int leftTarget = 0; - private int rightTarget = 0; - - // Calculate the COUNTS_PER_INCH for your specific drive train. - // Go to your motor vendor website to determine your motor's COUNTS_PER_MOTOR_REV - // For external drive gearing, set DRIVE_GEAR_REDUCTION as needed. - // For example, use a value of 2.0 for a 12-tooth spur gear driving a 24-tooth spur gear. - // This is gearing DOWN for less speed and more torque. - // For gearing UP, use a gear ratio less than 1.0. Note this will affect the direction of wheel rotation. - static final double COUNTS_PER_MOTOR_REV = 537.7 ; // eg: GoBILDA 312 RPM Yellow Jacket - static final double DRIVE_GEAR_REDUCTION = 1.0 ; // No External Gearing. - static final double WHEEL_DIAMETER_INCHES = 4.0 ; // For figuring circumference - static final double COUNTS_PER_INCH = (COUNTS_PER_MOTOR_REV * DRIVE_GEAR_REDUCTION) / - (WHEEL_DIAMETER_INCHES * 3.1415); - - // These constants define the desired driving/control characteristics - // They can/should be tweaked to suit the specific robot drive train. - static final double DRIVE_SPEED = 0.4; // Max driving speed for better distance accuracy. - static final double TURN_SPEED = 0.2; // Max turn speed to limit turn rate. - static final double HEADING_THRESHOLD = 1.0 ; // How close must the heading get to the target before moving to next step. - // Requiring more accuracy (a smaller number) will often make the turn take longer to get into the final position. - // Define the Proportional control coefficient (or GAIN) for "heading control". - // We define one value when Turning (larger errors), and the other is used when Driving straight (smaller errors). - // Increase these numbers if the heading does not correct strongly enough (eg: a heavy robot or using tracks) - // Decrease these numbers if the heading does not settle on the correct value (eg: very agile robot with omni wheels) - static final double P_TURN_GAIN = 0.02; // Larger is more responsive, but also less stable. - static final double P_DRIVE_GAIN = 0.03; // Larger is more responsive, but also less stable. - - - @Override - public void runOpMode() { - - // Initialize the drive system variables. - leftDrive = hardwareMap.get(DcMotor.class, "left_drive"); - rightDrive = hardwareMap.get(DcMotor.class, "right_drive"); - - // To drive forward, most robots need the motor on one side to be reversed, because the axles point in opposite directions. - // When run, this OpMode should start both motors driving forward. So adjust these two lines based on your first test drive. - // Note: The settings here assume direct drive on left and right wheels. Gear Reduction or 90 Deg drives may require direction flips - leftDrive.setDirection(DcMotor.Direction.REVERSE); - rightDrive.setDirection(DcMotor.Direction.FORWARD); - - /* The next two lines define Hub orientation. - * The Default Orientation (shown) is when a hub is mounted horizontally with the printed logo pointing UP and the USB port pointing FORWARD. - * - * To Do: EDIT these two lines to match YOUR mounting configuration. - */ - RevHubOrientationOnRobot.LogoFacingDirection logoDirection = RevHubOrientationOnRobot.LogoFacingDirection.UP; - RevHubOrientationOnRobot.UsbFacingDirection usbDirection = RevHubOrientationOnRobot.UsbFacingDirection.FORWARD; - RevHubOrientationOnRobot orientationOnRobot = new RevHubOrientationOnRobot(logoDirection, usbDirection); - - // Now initialize the IMU with this mounting orientation - // This sample expects the IMU to be in a REV Hub and named "imu". - imu = hardwareMap.get(IMU.class, "imu"); - imu.initialize(new IMU.Parameters(orientationOnRobot)); - - // Ensure the robot is stationary. Reset the encoders and set the motors to BRAKE mode - leftDrive.setMode(DcMotor.RunMode.STOP_AND_RESET_ENCODER); - rightDrive.setMode(DcMotor.RunMode.STOP_AND_RESET_ENCODER); - leftDrive.setZeroPowerBehavior(DcMotor.ZeroPowerBehavior.BRAKE); - rightDrive.setZeroPowerBehavior(DcMotor.ZeroPowerBehavior.BRAKE); - - // Wait for the game to start (Display Gyro value while waiting) - while (opModeInInit()) { - telemetry.addData(">", "Robot Heading = %4.0f", getHeading()); - telemetry.update(); - } - - // Set the encoders for closed loop speed control, and reset the heading. - leftDrive.setMode(DcMotor.RunMode.RUN_USING_ENCODER); - rightDrive.setMode(DcMotor.RunMode.RUN_USING_ENCODER); - imu.resetYaw(); - - // Step through each leg of the path, - // Notes: Reverse movement is obtained by setting a negative distance (not speed) - // holdHeading() is used after turns to let the heading stabilize - // Add a sleep(2000) after any step to keep the telemetry data visible for review - - driveStraight(DRIVE_SPEED, 24.0, 0.0); // Drive Forward 24" - turnToHeading( TURN_SPEED, -45.0); // Turn CW to -45 Degrees - holdHeading( TURN_SPEED, -45.0, 0.5); // Hold -45 Deg heading for a 1/2 second - - driveStraight(DRIVE_SPEED, 17.0, -45.0); // Drive Forward 17" at -45 degrees (12"x and 12"y) - turnToHeading( TURN_SPEED, 45.0); // Turn CCW to 45 Degrees - holdHeading( TURN_SPEED, 45.0, 0.5); // Hold 45 Deg heading for a 1/2 second - - driveStraight(DRIVE_SPEED, 17.0, 45.0); // Drive Forward 17" at 45 degrees (-12"x and 12"y) - turnToHeading( TURN_SPEED, 0.0); // Turn CW to 0 Degrees - holdHeading( TURN_SPEED, 0.0, 1.0); // Hold 0 Deg heading for 1 second - - driveStraight(DRIVE_SPEED,-48.0, 0.0); // Drive in Reverse 48" (should return to approx. staring position) - - telemetry.addData("Path", "Complete"); - telemetry.update(); - sleep(1000); // Pause to display last telemetry message. - } - - /* - * ==================================================================================================== - * Driving "Helper" functions are below this line. - * These provide the high and low level methods that handle driving straight and turning. - * ==================================================================================================== - */ - - // ********** HIGH Level driving functions. ******************** - - /** - * Drive in a straight line, on a fixed compass heading (angle), based on encoder counts. - * Move will stop if either of these conditions occur: - * 1) Move gets to the desired position - * 2) Driver stops the OpMode running. - * - * @param maxDriveSpeed MAX Speed for forward/rev motion (range 0 to +1.0) . - * @param distance Distance (in inches) to move from current position. Negative distance means move backward. - * @param heading Absolute Heading Angle (in Degrees) relative to last gyro reset. - * 0 = fwd. +ve is CCW from fwd. -ve is CW from forward. - * If a relative angle is required, add/subtract from the current robotHeading. - */ - public void driveStraight(double maxDriveSpeed, - double distance, - double heading) { - - // Ensure that the OpMode is still active - if (opModeIsActive()) { - - // Determine new target position, and pass to motor controller - int moveCounts = (int)(distance * COUNTS_PER_INCH); - leftTarget = leftDrive.getCurrentPosition() + moveCounts; - rightTarget = rightDrive.getCurrentPosition() + moveCounts; - - // Set Target FIRST, then turn on RUN_TO_POSITION - leftDrive.setTargetPosition(leftTarget); - rightDrive.setTargetPosition(rightTarget); - - leftDrive.setMode(DcMotor.RunMode.RUN_TO_POSITION); - rightDrive.setMode(DcMotor.RunMode.RUN_TO_POSITION); - - // Set the required driving speed (must be positive for RUN_TO_POSITION) - // Start driving straight, and then enter the control loop - maxDriveSpeed = Math.abs(maxDriveSpeed); - moveRobot(maxDriveSpeed, 0); - - // keep looping while we are still active, and BOTH motors are running. - while (opModeIsActive() && - (leftDrive.isBusy() && rightDrive.isBusy())) { - - // Determine required steering to keep on heading - turnSpeed = getSteeringCorrection(heading, P_DRIVE_GAIN); - - // if driving in reverse, the motor correction also needs to be reversed - if (distance < 0) - turnSpeed *= -1.0; - - // Apply the turning correction to the current driving speed. - moveRobot(driveSpeed, turnSpeed); - - // Display drive status for the driver. - sendTelemetry(true); - } - - // Stop all motion & Turn off RUN_TO_POSITION - moveRobot(0, 0); - leftDrive.setMode(DcMotor.RunMode.RUN_USING_ENCODER); - rightDrive.setMode(DcMotor.RunMode.RUN_USING_ENCODER); - } - } - - /** - * Spin on the central axis to point in a new direction. - *

- * Move will stop if either of these conditions occur: - *

- * 1) Move gets to the heading (angle) - *

- * 2) Driver stops the OpMode running. - * - * @param maxTurnSpeed Desired MAX speed of turn. (range 0 to +1.0) - * @param heading Absolute Heading Angle (in Degrees) relative to last gyro reset. - * 0 = fwd. +ve is CCW from fwd. -ve is CW from forward. - * If a relative angle is required, add/subtract from current heading. - */ - public void turnToHeading(double maxTurnSpeed, double heading) { - - // Run getSteeringCorrection() once to pre-calculate the current error - getSteeringCorrection(heading, P_DRIVE_GAIN); - - // keep looping while we are still active, and not on heading. - while (opModeIsActive() && (Math.abs(headingError) > HEADING_THRESHOLD)) { - - // Determine required steering to keep on heading - turnSpeed = getSteeringCorrection(heading, P_TURN_GAIN); - - // Clip the speed to the maximum permitted value. - turnSpeed = Range.clip(turnSpeed, -maxTurnSpeed, maxTurnSpeed); - - // Pivot in place by applying the turning correction - moveRobot(0, turnSpeed); - - // Display drive status for the driver. - sendTelemetry(false); - } - - // Stop all motion; - moveRobot(0, 0); - } - - /** - * Obtain & hold a heading for a finite amount of time - *

- * Move will stop once the requested time has elapsed - *

- * This function is useful for giving the robot a moment to stabilize its heading between movements. - * - * @param maxTurnSpeed Maximum differential turn speed (range 0 to +1.0) - * @param heading Absolute Heading Angle (in Degrees) relative to last gyro reset. - * 0 = fwd. +ve is CCW from fwd. -ve is CW from forward. - * If a relative angle is required, add/subtract from current heading. - * @param holdTime Length of time (in seconds) to hold the specified heading. - */ - public void holdHeading(double maxTurnSpeed, double heading, double holdTime) { - - ElapsedTime holdTimer = new ElapsedTime(); - holdTimer.reset(); - - // keep looping while we have time remaining. - while (opModeIsActive() && (holdTimer.time() < holdTime)) { - // Determine required steering to keep on heading - turnSpeed = getSteeringCorrection(heading, P_TURN_GAIN); - - // Clip the speed to the maximum permitted value. - turnSpeed = Range.clip(turnSpeed, -maxTurnSpeed, maxTurnSpeed); - - // Pivot in place by applying the turning correction - moveRobot(0, turnSpeed); - - // Display drive status for the driver. - sendTelemetry(false); - } - - // Stop all motion; - moveRobot(0, 0); - } - - // ********** LOW Level driving functions. ******************** - - /** - * Use a Proportional Controller to determine how much steering correction is required. - * - * @param desiredHeading The desired absolute heading (relative to last heading reset) - * @param proportionalGain Gain factor applied to heading error to obtain turning power. - * @return Turning power needed to get to required heading. - */ - public double getSteeringCorrection(double desiredHeading, double proportionalGain) { - targetHeading = desiredHeading; // Save for telemetry - - // Determine the heading current error - headingError = targetHeading - getHeading(); - - // Normalize the error to be within +/- 180 degrees - while (headingError > 180) headingError -= 360; - while (headingError <= -180) headingError += 360; - - // Multiply the error by the gain to determine the required steering correction/ Limit the result to +/- 1.0 - return Range.clip(headingError * proportionalGain, -1, 1); - } - - /** - * Take separate drive (fwd/rev) and turn (right/left) requests, - * combines them, and applies the appropriate speed commands to the left and right wheel motors. - * @param drive forward motor speed - * @param turn clockwise turning motor speed. - */ - public void moveRobot(double drive, double turn) { - driveSpeed = drive; // save this value as a class member so it can be used by telemetry. - turnSpeed = turn; // save this value as a class member so it can be used by telemetry. - - leftSpeed = drive - turn; - rightSpeed = drive + turn; - - // Scale speeds down if either one exceeds +/- 1.0; - double max = Math.max(Math.abs(leftSpeed), Math.abs(rightSpeed)); - if (max > 1.0) - { - leftSpeed /= max; - rightSpeed /= max; - } - - leftDrive.setPower(leftSpeed); - rightDrive.setPower(rightSpeed); - } - - /** - * Display the various control parameters while driving - * - * @param straight Set to true if we are driving straight, and the encoder positions should be included in the telemetry. - */ - private void sendTelemetry(boolean straight) { - - if (straight) { - telemetry.addData("Motion", "Drive Straight"); - telemetry.addData("Target Pos L:R", "%7d:%7d", leftTarget, rightTarget); - telemetry.addData("Actual Pos L:R", "%7d:%7d", leftDrive.getCurrentPosition(), - rightDrive.getCurrentPosition()); - } else { - telemetry.addData("Motion", "Turning"); - } - - telemetry.addData("Heading- Target : Current", "%5.2f : %5.0f", targetHeading, getHeading()); - telemetry.addData("Error : Steer Pwr", "%5.1f : %5.1f", headingError, turnSpeed); - telemetry.addData("Wheel Speeds L : R", "%5.2f : %5.2f", leftSpeed, rightSpeed); - telemetry.update(); - } - - /** - * read the Robot heading directly from the IMU (in degrees) - */ - public double getHeading() { - YawPitchRollAngles orientation = imu.getRobotYawPitchRollAngles(); - return orientation.getYaw(AngleUnit.DEGREES); - } -} diff --git a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/RobotAutoDriveByTime_Linear.java b/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/RobotAutoDriveByTime_Linear.java deleted file mode 100644 index a714748..0000000 --- a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/RobotAutoDriveByTime_Linear.java +++ /dev/null @@ -1,128 +0,0 @@ -/* Copyright (c) 2017 FIRST. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted (subject to the limitations in the disclaimer below) provided that - * the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list - * of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this - * list of conditions and the following disclaimer in the documentation and/or - * other materials provided with the distribution. - * - * Neither the name of FIRST nor the names of its contributors may be used to endorse or - * promote products derived from this software without specific prior written permission. - * - * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS - * LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.firstinspires.ftc.robotcontroller.external.samples; - -import com.qualcomm.robotcore.eventloop.opmode.Autonomous; -import com.qualcomm.robotcore.eventloop.opmode.Disabled; -import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode; -import com.qualcomm.robotcore.hardware.DcMotor; -import com.qualcomm.robotcore.util.ElapsedTime; - -/* - * This OpMode illustrates the concept of driving a path based on time. - * The code is structured as a LinearOpMode - * - * The code assumes that you do NOT have encoders on the wheels, - * otherwise you would use: RobotAutoDriveByEncoder; - * - * The desired path in this example is: - * - Drive forward for 3 seconds - * - Spin right for 1.3 seconds - * - Drive Backward for 1 Second - * - * The code is written in a simple form with no optimizations. - * However, there are several ways that this type of sequence could be streamlined, - * - * Use Android Studio to Copy this Class, and Paste it into your team's code folder with a new name. - * Remove or comment out the @Disabled line to add this OpMode to the Driver Station OpMode list - */ - -@Autonomous(name="Robot: Auto Drive By Time", group="Robot") -@Disabled -public class RobotAutoDriveByTime_Linear extends LinearOpMode { - - /* Declare OpMode members. */ - private DcMotor leftDrive = null; - private DcMotor rightDrive = null; - - private ElapsedTime runtime = new ElapsedTime(); - - - static final double FORWARD_SPEED = 0.6; - static final double TURN_SPEED = 0.5; - - @Override - public void runOpMode() { - - // Initialize the drive system variables. - leftDrive = hardwareMap.get(DcMotor.class, "left_drive"); - rightDrive = hardwareMap.get(DcMotor.class, "right_drive"); - - // To drive forward, most robots need the motor on one side to be reversed, because the axles point in opposite directions. - // When run, this OpMode should start both motors driving forward. So adjust these two lines based on your first test drive. - // Note: The settings here assume direct drive on left and right wheels. Gear Reduction or 90 Deg drives may require direction flips - leftDrive.setDirection(DcMotor.Direction.REVERSE); - rightDrive.setDirection(DcMotor.Direction.FORWARD); - - // Send telemetry message to signify robot waiting; - telemetry.addData("Status", "Ready to run"); // - telemetry.update(); - - // Wait for the game to start (driver presses START) - waitForStart(); - - // Step through each leg of the path, ensuring that the OpMode has not been stopped along the way. - - // Step 1: Drive forward for 3 seconds - leftDrive.setPower(FORWARD_SPEED); - rightDrive.setPower(FORWARD_SPEED); - runtime.reset(); - while (opModeIsActive() && (runtime.seconds() < 3.0)) { - telemetry.addData("Path", "Leg 1: %4.1f S Elapsed", runtime.seconds()); - telemetry.update(); - } - - // Step 2: Spin right for 1.3 seconds - leftDrive.setPower(TURN_SPEED); - rightDrive.setPower(-TURN_SPEED); - runtime.reset(); - while (opModeIsActive() && (runtime.seconds() < 1.3)) { - telemetry.addData("Path", "Leg 2: %4.1f S Elapsed", runtime.seconds()); - telemetry.update(); - } - - // Step 3: Drive Backward for 1 Second - leftDrive.setPower(-FORWARD_SPEED); - rightDrive.setPower(-FORWARD_SPEED); - runtime.reset(); - while (opModeIsActive() && (runtime.seconds() < 1.0)) { - telemetry.addData("Path", "Leg 3: %4.1f S Elapsed", runtime.seconds()); - telemetry.update(); - } - - // Step 4: Stop - leftDrive.setPower(0); - rightDrive.setPower(0); - - telemetry.addData("Path", "Complete"); - telemetry.update(); - sleep(1000); - } -} diff --git a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/RobotAutoDriveToAprilTagOmni.java b/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/RobotAutoDriveToAprilTagOmni.java deleted file mode 100644 index 9bac006..0000000 --- a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/RobotAutoDriveToAprilTagOmni.java +++ /dev/null @@ -1,321 +0,0 @@ -/* Copyright (c) 2023 FIRST. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted (subject to the limitations in the disclaimer below) provided that - * the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list - * of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this - * list of conditions and the following disclaimer in the documentation and/or - * other materials provided with the distribution. - * - * Neither the name of FIRST nor the names of its contributors may be used to endorse or - * promote products derived from this software without specific prior written permission. - * - * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS - * LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.firstinspires.ftc.robotcontroller.external.samples; - -import com.qualcomm.robotcore.eventloop.opmode.Disabled; -import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode; -import com.qualcomm.robotcore.eventloop.opmode.TeleOp; -import com.qualcomm.robotcore.hardware.DcMotor; -import com.qualcomm.robotcore.util.Range; -import org.firstinspires.ftc.robotcore.external.hardware.camera.BuiltinCameraDirection; -import org.firstinspires.ftc.robotcore.external.hardware.camera.WebcamName; -import org.firstinspires.ftc.robotcore.external.hardware.camera.controls.ExposureControl; -import org.firstinspires.ftc.robotcore.external.hardware.camera.controls.GainControl; -import org.firstinspires.ftc.vision.VisionPortal; -import org.firstinspires.ftc.vision.apriltag.AprilTagDetection; -import org.firstinspires.ftc.vision.apriltag.AprilTagProcessor; - -import java.util.List; -import java.util.concurrent.TimeUnit; - -/* - * This OpMode illustrates using a camera to locate and drive towards a specific AprilTag. - * The code assumes a Holonomic (Mecanum or X Drive) Robot. - * - * For an introduction to AprilTags, see the ftc-docs link below: - * https://ftc-docs.firstinspires.org/en/latest/apriltag/vision_portal/apriltag_intro/apriltag-intro.html - * - * When an AprilTag in the TagLibrary is detected, the SDK provides location and orientation of the tag, relative to the camera. - * This information is provided in the "ftcPose" member of the returned "detection", and is explained in the ftc-docs page linked below. - * https://ftc-docs.firstinspires.org/apriltag-detection-values - * - * The drive goal is to rotate to keep the Tag centered in the camera, while strafing to be directly in front of the tag, and - * driving towards the tag to achieve the desired distance. - * To reduce any motion blur (which will interrupt the detection process) the Camera exposure is reduced to a very low value (5mS) - * You can determine the best Exposure and Gain values by using the ConceptAprilTagOptimizeExposure OpMode in this Samples folder. - * - * The code assumes a Robot Configuration with motors named: leftfront_drive and rightfront_drive, leftback_drive and rightback_drive. - * The motor directions must be set so a positive power goes forward on all wheels. - * This sample assumes that the current game AprilTag Library (usually for the current season) is being loaded by default, - * so you should choose to approach a valid tag ID. - * - * Under manual control, the left stick will move forward/back & left/right. The right stick will rotate the robot. - * Manually drive the robot until it displays Target data on the Driver Station. - * - * Press and hold the *Left Bumper* to enable the automatic "Drive to target" mode. - * Release the Left Bumper to return to manual driving mode. - * - * Under "Drive To Target" mode, the robot has three goals: - * 1) Turn the robot to always keep the Tag centered on the camera frame. (Use the Target Bearing to turn the robot.) - * 2) Strafe the robot towards the centerline of the Tag, so it approaches directly in front of the tag. (Use the Target Yaw to strafe the robot) - * 3) Drive towards the Tag to get to the desired distance. (Use Tag Range to drive the robot forward/backward) - * - * Use DESIRED_DISTANCE to set how close you want the robot to get to the target. - * Speed and Turn sensitivity can be adjusted using the SPEED_GAIN, STRAFE_GAIN and TURN_GAIN constants. - * - * Use Android Studio to Copy this Class, and Paste it into the TeamCode/src/main/java/org/firstinspires/ftc/teamcode folder. - * Remove or comment out the @Disabled line to add this OpMode to the Driver Station OpMode list. - * - */ - -@TeleOp(name="Omni Drive To AprilTag", group = "Concept") -@Disabled -public class RobotAutoDriveToAprilTagOmni extends LinearOpMode -{ - // Adjust these numbers to suit your robot. - final double DESIRED_DISTANCE = 12.0; // this is how close the camera should get to the target (inches) - - // Set the GAIN constants to control the relationship between the measured position error, and how much power is - // applied to the drive motors to correct the error. - // Drive = Error * Gain Make these values smaller for smoother control, or larger for a more aggressive response. - final double SPEED_GAIN = 0.02 ; // Forward Speed Control "Gain". e.g. Ramp up to 50% power at a 25 inch error. (0.50 / 25.0) - final double STRAFE_GAIN = 0.015 ; // Strafe Speed Control "Gain". e.g. Ramp up to 37% power at a 25 degree Yaw error. (0.375 / 25.0) - final double TURN_GAIN = 0.01 ; // Turn Control "Gain". e.g. Ramp up to 25% power at a 25 degree error. (0.25 / 25.0) - - final double MAX_AUTO_SPEED = 0.5; // Clip the approach speed to this max value (adjust for your robot) - final double MAX_AUTO_STRAFE= 0.5; // Clip the strafing speed to this max value (adjust for your robot) - final double MAX_AUTO_TURN = 0.3; // Clip the turn speed to this max value (adjust for your robot) - - private DcMotor leftFrontDrive = null; // Used to control the left front drive wheel - private DcMotor rightFrontDrive = null; // Used to control the right front drive wheel - private DcMotor leftBackDrive = null; // Used to control the left back drive wheel - private DcMotor rightBackDrive = null; // Used to control the right back drive wheel - - private static final boolean USE_WEBCAM = true; // Set true to use a webcam, or false for a phone camera - private static final int DESIRED_TAG_ID = -1; // Choose the tag you want to approach or set to -1 for ANY tag. - private VisionPortal visionPortal; // Used to manage the video source. - private AprilTagProcessor aprilTag; // Used for managing the AprilTag detection process. - private AprilTagDetection desiredTag = null; // Used to hold the data for a detected AprilTag - - @Override public void runOpMode() - { - boolean targetFound = false; // Set to true when an AprilTag target is detected - double drive = 0; // Desired forward power/speed (-1 to +1) - double strafe = 0; // Desired strafe power/speed (-1 to +1) - double turn = 0; // Desired turning power/speed (-1 to +1) - - // Initialize the Apriltag Detection process - initAprilTag(); - - // Initialize the hardware variables. Note that the strings used here as parameters - // to 'get' must match the names assigned during the robot configuration. - // step (using the FTC Robot Controller app on the phone). - leftFrontDrive = hardwareMap.get(DcMotor.class, "leftfront_drive"); - rightFrontDrive = hardwareMap.get(DcMotor.class, "rightfront_drive"); - leftBackDrive = hardwareMap.get(DcMotor.class, "leftback_drive"); - rightBackDrive = hardwareMap.get(DcMotor.class, "rightback_drive"); - - // To drive forward, most robots need the motor on one side to be reversed, because the axles point in opposite directions. - // When run, this OpMode should start both motors driving forward. So adjust these two lines based on your first test drive. - // Note: The settings here assume direct drive on left and right wheels. Gear Reduction or 90 Deg drives may require direction flips - leftFrontDrive.setDirection(DcMotor.Direction.REVERSE); - leftBackDrive.setDirection(DcMotor.Direction.REVERSE); - rightFrontDrive.setDirection(DcMotor.Direction.FORWARD); - rightBackDrive.setDirection(DcMotor.Direction.FORWARD); - - if (USE_WEBCAM) - setManualExposure(6, 250); // Use low exposure time to reduce motion blur - - // Wait for driver to press start - telemetry.addData("Camera preview on/off", "3 dots, Camera Stream"); - telemetry.addData(">", "Touch START to start OpMode"); - telemetry.update(); - waitForStart(); - - while (opModeIsActive()) - { - targetFound = false; - desiredTag = null; - - // Step through the list of detected tags and look for a matching tag - List currentDetections = aprilTag.getDetections(); - for (AprilTagDetection detection : currentDetections) { - // Look to see if we have size info on this tag. - if (detection.metadata != null) { - // Check to see if we want to track towards this tag. - if ((DESIRED_TAG_ID < 0) || (detection.id == DESIRED_TAG_ID)) { - // Yes, we want to use this tag. - targetFound = true; - desiredTag = detection; - break; // don't look any further. - } else { - // This tag is in the library, but we do not want to track it right now. - telemetry.addData("Skipping", "Tag ID %d is not desired", detection.id); - } - } else { - // This tag is NOT in the library, so we don't have enough information to track to it. - telemetry.addData("Unknown", "Tag ID %d is not in TagLibrary", detection.id); - } - } - - // Tell the driver what we see, and what to do. - if (targetFound) { - telemetry.addData("\n>","HOLD Left-Bumper to Drive to Target\n"); - telemetry.addData("Found", "ID %d (%s)", desiredTag.id, desiredTag.metadata.name); - telemetry.addData("Range", "%5.1f inches", desiredTag.ftcPose.range); - telemetry.addData("Bearing","%3.0f degrees", desiredTag.ftcPose.bearing); - telemetry.addData("Yaw","%3.0f degrees", desiredTag.ftcPose.yaw); - } else { - telemetry.addData("\n>","Drive using joysticks to find valid target\n"); - } - - // If Left Bumper is being pressed, AND we have found the desired target, Drive to target Automatically . - if (gamepad1.left_bumper && targetFound) { - - // Determine heading, range and Yaw (tag image rotation) error so we can use them to control the robot automatically. - double rangeError = (desiredTag.ftcPose.range - DESIRED_DISTANCE); - double headingError = desiredTag.ftcPose.bearing; - double yawError = desiredTag.ftcPose.yaw; - - // Use the speed and turn "gains" to calculate how we want the robot to move. - drive = Range.clip(rangeError * SPEED_GAIN, -MAX_AUTO_SPEED, MAX_AUTO_SPEED); - turn = Range.clip(headingError * TURN_GAIN, -MAX_AUTO_TURN, MAX_AUTO_TURN) ; - strafe = Range.clip(-yawError * STRAFE_GAIN, -MAX_AUTO_STRAFE, MAX_AUTO_STRAFE); - - telemetry.addData("Auto","Drive %5.2f, Strafe %5.2f, Turn %5.2f ", drive, strafe, turn); - } else { - - // drive using manual POV Joystick mode. Slow things down to make the robot more controlable. - drive = -gamepad1.left_stick_y / 2.0; // Reduce drive rate to 50%. - strafe = -gamepad1.left_stick_x / 2.0; // Reduce strafe rate to 50%. - turn = -gamepad1.right_stick_x / 3.0; // Reduce turn rate to 33%. - telemetry.addData("Manual","Drive %5.2f, Strafe %5.2f, Turn %5.2f ", drive, strafe, turn); - } - telemetry.update(); - - // Apply desired axes motions to the drivetrain. - moveRobot(drive, strafe, turn); - sleep(10); - } - } - - /** - * Move robot according to desired axes motions - *

- * Positive X is forward - *

- * Positive Y is strafe left - *

- * Positive Yaw is counter-clockwise - */ - public void moveRobot(double x, double y, double yaw) { - // Calculate wheel powers. - double leftFrontPower = x -y -yaw; - double rightFrontPower = x +y +yaw; - double leftBackPower = x +y -yaw; - double rightBackPower = x -y +yaw; - - // Normalize wheel powers to be less than 1.0 - double max = Math.max(Math.abs(leftFrontPower), Math.abs(rightFrontPower)); - max = Math.max(max, Math.abs(leftBackPower)); - max = Math.max(max, Math.abs(rightBackPower)); - - if (max > 1.0) { - leftFrontPower /= max; - rightFrontPower /= max; - leftBackPower /= max; - rightBackPower /= max; - } - - // Send powers to the wheels. - leftFrontDrive.setPower(leftFrontPower); - rightFrontDrive.setPower(rightFrontPower); - leftBackDrive.setPower(leftBackPower); - rightBackDrive.setPower(rightBackPower); - } - - /** - * Initialize the AprilTag processor. - */ - private void initAprilTag() { - // Create the AprilTag processor by using a builder. - aprilTag = new AprilTagProcessor.Builder().build(); - - // Adjust Image Decimation to trade-off detection-range for detection-rate. - // e.g. Some typical detection data using a Logitech C920 WebCam - // Decimation = 1 .. Detect 2" Tag from 10 feet away at 10 Frames per second - // Decimation = 2 .. Detect 2" Tag from 6 feet away at 22 Frames per second - // Decimation = 3 .. Detect 2" Tag from 4 feet away at 30 Frames Per Second - // Decimation = 3 .. Detect 5" Tag from 10 feet away at 30 Frames Per Second - // Note: Decimation can be changed on-the-fly to adapt during a match. - aprilTag.setDecimation(2); - - // Create the vision portal by using a builder. - if (USE_WEBCAM) { - visionPortal = new VisionPortal.Builder() - .setCamera(hardwareMap.get(WebcamName.class, "Webcam 1")) - .addProcessor(aprilTag) - .build(); - } else { - visionPortal = new VisionPortal.Builder() - .setCamera(BuiltinCameraDirection.BACK) - .addProcessor(aprilTag) - .build(); - } - } - - /* - Manually set the camera gain and exposure. - This can only be called AFTER calling initAprilTag(), and only works for Webcams; - */ - private void setManualExposure(int exposureMS, int gain) { - // Wait for the camera to be open, then use the controls - - if (visionPortal == null) { - return; - } - - // Make sure camera is streaming before we try to set the exposure controls - if (visionPortal.getCameraState() != VisionPortal.CameraState.STREAMING) { - telemetry.addData("Camera", "Waiting"); - telemetry.update(); - while (!isStopRequested() && (visionPortal.getCameraState() != VisionPortal.CameraState.STREAMING)) { - sleep(20); - } - telemetry.addData("Camera", "Ready"); - telemetry.update(); - } - - // Set camera controls unless we are stopping. - if (!isStopRequested()) - { - ExposureControl exposureControl = visionPortal.getCameraControl(ExposureControl.class); - if (exposureControl.getMode() != ExposureControl.Mode.Manual) { - exposureControl.setMode(ExposureControl.Mode.Manual); - sleep(50); - } - exposureControl.setExposure((long)exposureMS, TimeUnit.MILLISECONDS); - sleep(20); - GainControl gainControl = visionPortal.getCameraControl(GainControl.class); - gainControl.setGain(gain); - sleep(20); - } - } -} diff --git a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/RobotAutoDriveToAprilTagTank.java b/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/RobotAutoDriveToAprilTagTank.java deleted file mode 100644 index ba3eb4f..0000000 --- a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/RobotAutoDriveToAprilTagTank.java +++ /dev/null @@ -1,298 +0,0 @@ -/* Copyright (c) 2023 FIRST. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted (subject to the limitations in the disclaimer below) provided that - * the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list - * of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this - * list of conditions and the following disclaimer in the documentation and/or - * other materials provided with the distribution. - * - * Neither the name of FIRST nor the names of its contributors may be used to endorse or - * promote products derived from this software without specific prior written permission. - * - * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS - * LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.firstinspires.ftc.robotcontroller.external.samples; - -import com.qualcomm.robotcore.eventloop.opmode.Disabled; -import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode; -import com.qualcomm.robotcore.eventloop.opmode.TeleOp; -import com.qualcomm.robotcore.hardware.DcMotor; -import com.qualcomm.robotcore.util.Range; -import org.firstinspires.ftc.robotcore.external.hardware.camera.BuiltinCameraDirection; -import org.firstinspires.ftc.robotcore.external.hardware.camera.WebcamName; -import org.firstinspires.ftc.robotcore.external.hardware.camera.controls.ExposureControl; -import org.firstinspires.ftc.robotcore.external.hardware.camera.controls.GainControl; -import org.firstinspires.ftc.vision.VisionPortal; -import org.firstinspires.ftc.vision.apriltag.AprilTagDetection; -import org.firstinspires.ftc.vision.apriltag.AprilTagProcessor; - -import java.util.List; -import java.util.concurrent.TimeUnit; - -/* - * This OpMode illustrates using a camera to locate and drive towards a specific AprilTag. - * The code assumes a basic two-wheel (Tank) Robot Drivetrain - * - * For an introduction to AprilTags, see the ftc-docs link below: - * https://ftc-docs.firstinspires.org/en/latest/apriltag/vision_portal/apriltag_intro/apriltag-intro.html - * - * When an AprilTag in the TagLibrary is detected, the SDK provides location and orientation of the tag, relative to the camera. - * This information is provided in the "ftcPose" member of the returned "detection", and is explained in the ftc-docs page linked below. - * https://ftc-docs.firstinspires.org/apriltag-detection-values - * - * The driving goal is to rotate to keep the tag centered in the camera, while driving towards the tag to achieve the desired distance. - * To reduce any motion blur (which will interrupt the detection process) the Camera exposure is reduced to a very low value (5mS) - * You can determine the best exposure and gain values by using the ConceptAprilTagOptimizeExposure OpMode in this Samples folder. - * - * The code assumes a Robot Configuration with motors named left_drive and right_drive. - * The motor directions must be set so a positive power goes forward on both wheels; - * This sample assumes that the default AprilTag Library (usually for the current season) is being loaded by default - * so you should choose to approach a valid tag ID. - * - * Under manual control, the left stick will move forward/back, and the right stick will rotate the robot. - * This is called POV Joystick mode, different than Tank Drive (where each joystick controls a wheel). - * - * Manually drive the robot until it displays Target data on the Driver Station. - * Press and hold the *Left Bumper* to enable the automatic "Drive to target" mode. - * Release the Left Bumper to return to manual driving mode. - * - * Under "Drive To Target" mode, the robot has two goals: - * 1) Turn the robot to always keep the Tag centered on the camera frame. (Use the Target Bearing to turn the robot.) - * 2) Drive towards the Tag to get to the desired distance. (Use Tag Range to drive the robot forward/backward) - * - * Use DESIRED_DISTANCE to set how close you want the robot to get to the target. - * Speed and Turn sensitivity can be adjusted using the SPEED_GAIN and TURN_GAIN constants. - * - * Use Android Studio to Copy this Class, and Paste it into the TeamCode/src/main/java/org/firstinspires/ftc/teamcode folder. - * Remove or comment out the @Disabled line to add this OpMode to the Driver Station OpMode list. - * - */ - -@TeleOp(name="Tank Drive To AprilTag", group = "Concept") -@Disabled -public class RobotAutoDriveToAprilTagTank extends LinearOpMode -{ - // Adjust these numbers to suit your robot. - final double DESIRED_DISTANCE = 12.0; // this is how close the camera should get to the target (inches) - - // Set the GAIN constants to control the relationship between the measured position error, and how much power is - // applied to the drive motors to correct the error. - // Drive = Error * Gain Make these values smaller for smoother control, or larger for a more aggressive response. - final double SPEED_GAIN = 0.02 ; // Speed Control "Gain". e.g. Ramp up to 50% power at a 25 inch error. (0.50 / 25.0) - final double TURN_GAIN = 0.01 ; // Turn Control "Gain". e.g. Ramp up to 25% power at a 25 degree error. (0.25 / 25.0) - - final double MAX_AUTO_SPEED = 0.5; // Clip the approach speed to this max value (adjust for your robot) - final double MAX_AUTO_TURN = 0.25; // Clip the turn speed to this max value (adjust for your robot) - - private DcMotor leftDrive = null; // Used to control the left drive wheel - private DcMotor rightDrive = null; // Used to control the right drive wheel - - private static final boolean USE_WEBCAM = true; // Set true to use a webcam, or false for a phone camera - private static final int DESIRED_TAG_ID = -1; // Choose the tag you want to approach or set to -1 for ANY tag. - private VisionPortal visionPortal; // Used to manage the video source. - private AprilTagProcessor aprilTag; // Used for managing the AprilTag detection process. - private AprilTagDetection desiredTag = null; // Used to hold the data for a detected AprilTag - - @Override public void runOpMode() - { - boolean targetFound = false; // Set to true when an AprilTag target is detected - double drive = 0; // Desired forward power/speed (-1 to +1) +ve is forward - double turn = 0; // Desired turning power/speed (-1 to +1) +ve is CounterClockwise - - // Initialize the Apriltag Detection process - initAprilTag(); - - // Initialize the hardware variables. Note that the strings used here as parameters - // to 'get' must match the names assigned during the robot configuration. - // step (using the FTC Robot Controller app on the phone). - leftDrive = hardwareMap.get(DcMotor.class, "left_drive"); - rightDrive = hardwareMap.get(DcMotor.class, "right_drive"); - - // To drive forward, most robots need the motor on one side to be reversed because the axles point in opposite directions. - // When run, this OpMode should start both motors driving forward. So adjust these two lines based on your first test drive. - // Note: The settings here assume direct drive on left and right wheels. Single Gear Reduction or 90 Deg drives may require direction flips - leftDrive.setDirection(DcMotor.Direction.REVERSE); - rightDrive.setDirection(DcMotor.Direction.FORWARD); - - if (USE_WEBCAM) - setManualExposure(6, 250); // Use low exposure time to reduce motion blur - - // Wait for the driver to press Start - telemetry.addData("Camera preview on/off", "3 dots, Camera Stream"); - telemetry.addData(">", "Touch START to start OpMode"); - telemetry.update(); - waitForStart(); - - while (opModeIsActive()) - { - targetFound = false; - desiredTag = null; - - // Step through the list of detected tags and look for a matching tag - List currentDetections = aprilTag.getDetections(); - for (AprilTagDetection detection : currentDetections) { - // Look to see if we have size info on this tag. - if (detection.metadata != null) { - // Check to see if we want to track towards this tag. - if ((DESIRED_TAG_ID < 0) || (detection.id == DESIRED_TAG_ID)) { - // Yes, we want to use this tag. - targetFound = true; - desiredTag = detection; - break; // don't look any further. - } else { - // This tag is in the library, but we do not want to track it right now. - telemetry.addData("Skipping", "Tag ID %d is not desired", detection.id); - } - } else { - // This tag is NOT in the library, so we don't have enough information to track to it. - telemetry.addData("Unknown", "Tag ID %d is not in TagLibrary", detection.id); - } - } - - // Tell the driver what we see, and what to do. - if (targetFound) { - telemetry.addData("\n>","HOLD Left-Bumper to Drive to Target\n"); - telemetry.addData("Found", "ID %d (%s)", desiredTag.id, desiredTag.metadata.name); - telemetry.addData("Range", "%5.1f inches", desiredTag.ftcPose.range); - telemetry.addData("Bearing","%3.0f degrees", desiredTag.ftcPose.bearing); - } else { - telemetry.addData("\n>","Drive using joysticks to find valid target\n"); - } - - // If Left Bumper is being pressed, AND we have found the desired target, Drive to target Automatically . - if (gamepad1.left_bumper && targetFound) { - - // Determine heading and range error so we can use them to control the robot automatically. - double rangeError = (desiredTag.ftcPose.range - DESIRED_DISTANCE); - double headingError = desiredTag.ftcPose.bearing; - - // Use the speed and turn "gains" to calculate how we want the robot to move. Clip it to the maximum - drive = Range.clip(rangeError * SPEED_GAIN, -MAX_AUTO_SPEED, MAX_AUTO_SPEED); - turn = Range.clip(headingError * TURN_GAIN, -MAX_AUTO_TURN, MAX_AUTO_TURN) ; - - telemetry.addData("Auto","Drive %5.2f, Turn %5.2f", drive, turn); - } else { - - // drive using manual POV Joystick mode. - drive = -gamepad1.left_stick_y / 2.0; // Reduce drive rate to 50%. - turn = -gamepad1.right_stick_x / 4.0; // Reduce turn rate to 25%. - telemetry.addData("Manual","Drive %5.2f, Turn %5.2f", drive, turn); - } - telemetry.update(); - - // Apply desired axes motions to the drivetrain. - moveRobot(drive, turn); - sleep(10); - } - } - - /** - * Move robot according to desired axes motions - *

- * Positive X is forward - *

- * Positive Yaw is counter-clockwise - */ - public void moveRobot(double x, double yaw) { - // Calculate left and right wheel powers. - double leftPower = x - yaw; - double rightPower = x + yaw; - - // Normalize wheel powers to be less than 1.0 - double max = Math.max(Math.abs(leftPower), Math.abs(rightPower)); - if (max >1.0) { - leftPower /= max; - rightPower /= max; - } - - // Send powers to the wheels. - leftDrive.setPower(leftPower); - rightDrive.setPower(rightPower); - } - - /** - * Initialize the AprilTag processor. - */ - private void initAprilTag() { - // Create the AprilTag processor by using a builder. - aprilTag = new AprilTagProcessor.Builder().build(); - - // Adjust Image Decimation to trade-off detection-range for detection-rate. - // e.g. Some typical detection data using a Logitech C920 WebCam - // Decimation = 1 .. Detect 2" Tag from 10 feet away at 10 Frames per second - // Decimation = 2 .. Detect 2" Tag from 6 feet away at 22 Frames per second - // Decimation = 3 .. Detect 2" Tag from 4 feet away at 30 Frames Per Second - // Decimation = 3 .. Detect 5" Tag from 10 feet away at 30 Frames Per Second - // Note: Decimation can be changed on-the-fly to adapt during a match. - aprilTag.setDecimation(2); - - // Create the vision portal by using a builder. - if (USE_WEBCAM) { - visionPortal = new VisionPortal.Builder() - .setCamera(hardwareMap.get(WebcamName.class, "Webcam 1")) - .addProcessor(aprilTag) - .build(); - } else { - visionPortal = new VisionPortal.Builder() - .setCamera(BuiltinCameraDirection.BACK) - .addProcessor(aprilTag) - .build(); - } - } - - /* - Manually set the camera gain and exposure. - This can only be called AFTER calling initAprilTag(), and only works for Webcams; - */ - private void setManualExposure(int exposureMS, int gain) { - // Wait for the camera to be open, then use the controls - - if (visionPortal == null) { - return; - } - - // Make sure camera is streaming before we try to set the exposure controls - if (visionPortal.getCameraState() != VisionPortal.CameraState.STREAMING) { - telemetry.addData("Camera", "Waiting"); - telemetry.update(); - while (!isStopRequested() && (visionPortal.getCameraState() != VisionPortal.CameraState.STREAMING)) { - sleep(20); - } - telemetry.addData("Camera", "Ready"); - telemetry.update(); - } - - // Set camera controls unless we are stopping. - if (!isStopRequested()) - { - ExposureControl exposureControl = visionPortal.getCameraControl(ExposureControl.class); - if (exposureControl.getMode() != ExposureControl.Mode.Manual) { - exposureControl.setMode(ExposureControl.Mode.Manual); - sleep(50); - } - exposureControl.setExposure((long)exposureMS, TimeUnit.MILLISECONDS); - sleep(20); - GainControl gainControl = visionPortal.getCameraControl(GainControl.class); - gainControl.setGain(gain); - sleep(20); - telemetry.addData("Camera", "Ready"); - telemetry.update(); - } - } -} diff --git a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/RobotAutoDriveToLine_Linear.java b/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/RobotAutoDriveToLine_Linear.java deleted file mode 100644 index 780f260..0000000 --- a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/RobotAutoDriveToLine_Linear.java +++ /dev/null @@ -1,142 +0,0 @@ -/* Copyright (c) 2017 FIRST. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted (subject to the limitations in the disclaimer below) provided that - * the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list - * of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this - * list of conditions and the following disclaimer in the documentation and/or - * other materials provided with the distribution. - * - * Neither the name of FIRST nor the names of its contributors may be used to endorse or - * promote products derived from this software without specific prior written permission. - * - * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS - * LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.firstinspires.ftc.robotcontroller.external.samples; - -import com.qualcomm.robotcore.eventloop.opmode.Autonomous; -import com.qualcomm.robotcore.eventloop.opmode.Disabled; -import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode; -import com.qualcomm.robotcore.hardware.DcMotor; -import com.qualcomm.robotcore.hardware.NormalizedColorSensor; -import com.qualcomm.robotcore.hardware.NormalizedRGBA; -import com.qualcomm.robotcore.hardware.SwitchableLight; - -/* - * This OpMode illustrates the concept of driving up to a line and then stopping. - * The code is structured as a LinearOpMode - * - * The Sensor used here can be a REV Color Sensor V2 or V3. Make sure the white LED is turned on. - * The sensor can be plugged into any I2C port, and must be named "sensor_color" in the active configuration. - * - * Depending on the height of your color sensor, you may want to set the sensor "gain". - * The higher the gain, the greater the reflected light reading will be. - * Use the SensorColor sample in this folder to determine the minimum gain value that provides an - * "Alpha" reading of 1.0 when you are on top of the white line. In this sample, we use a gain of 15 - * which works well with a Rev V2 color sensor - * - * Setting the correct WHITE_THRESHOLD value is key to stopping correctly. - * This should be set halfway between the bare-tile, and white-line "Alpha" values. - * The reflected light value can be read on the screen once the OpMode has been INIT, but before it is STARTED. - * Move the sensor on and off the white line and note the min and max readings. - * Edit this code to make WHITE_THRESHOLD halfway between the min and max. - * - * Use Android Studio to Copy this Class, and Paste it into your team's code folder with a new name. - * Remove or comment out the @Disabled line to add this OpMode to the Driver Station OpMode list - */ - -@Autonomous(name="Robot: Auto Drive To Line", group="Robot") -@Disabled -public class RobotAutoDriveToLine_Linear extends LinearOpMode { - - /* Declare OpMode members. */ - private DcMotor leftDrive = null; - private DcMotor rightDrive = null; - - /** The variable to store a reference to our color sensor hardware object */ - NormalizedColorSensor colorSensor; - - static final double WHITE_THRESHOLD = 0.5; // spans between 0.0 - 1.0 from dark to light - static final double APPROACH_SPEED = 0.25; - - @Override - public void runOpMode() { - - // Initialize the drive system variables. - leftDrive = hardwareMap.get(DcMotor.class, "left_drive"); - rightDrive = hardwareMap.get(DcMotor.class, "right_drive"); - - // To drive forward, most robots need the motor on one side to be reversed, because the axles point in opposite directions. - // When run, this OpMode should start both motors driving forward. So adjust these two lines based on your first test drive. - // Note: The settings here assume direct drive on left and right wheels. Gear Reduction or 90 Deg drives may require direction flips - leftDrive.setDirection(DcMotor.Direction.REVERSE); - rightDrive.setDirection(DcMotor.Direction.FORWARD); - - // If there are encoders connected, switch to RUN_USING_ENCODER mode for greater accuracy - // leftDrive.setMode(DcMotor.RunMode.RUN_USING_ENCODER); - // rightDrive.setMode(DcMotor.RunMode.RUN_USING_ENCODER); - - // Get a reference to our sensor object. It's recommended to use NormalizedColorSensor over - // ColorSensor, because NormalizedColorSensor consistently gives values between 0 and 1, while - // the values you get from ColorSensor are dependent on the specific sensor you're using. - colorSensor = hardwareMap.get(NormalizedColorSensor.class, "sensor_color"); - - // If necessary, turn ON the white LED (if there is no LED switch on the sensor) - if (colorSensor instanceof SwitchableLight) { - ((SwitchableLight)colorSensor).enableLight(true); - } - - // Some sensors allow you to set your light sensor gain for optimal sensitivity... - // See the SensorColor sample in this folder for how to determine the optimal gain. - // A gain of 15 causes a Rev Color Sensor V2 to produce an Alpha value of 1.0 at about 1.5" above the floor. - colorSensor.setGain(15); - - // Wait for driver to press START) - // Abort this loop is started or stopped. - while (opModeInInit()) { - - // Send telemetry message to signify robot waiting; - telemetry.addData("Status", "Ready to drive to white line."); // - - // Display the light level while we are waiting to start - getBrightness(); - } - - // Start the robot moving forward, and then begin looking for a white line. - leftDrive.setPower(APPROACH_SPEED); - rightDrive.setPower(APPROACH_SPEED); - - // run until the white line is seen OR the driver presses STOP; - while (opModeIsActive() && (getBrightness() < WHITE_THRESHOLD)) { - sleep(5); - } - - // Stop all motors - leftDrive.setPower(0); - rightDrive.setPower(0); - } - - // to obtain reflected light, read the normalized values from the color sensor. Return the Alpha channel. - double getBrightness() { - NormalizedRGBA colors = colorSensor.getNormalizedColors(); - telemetry.addData("Light Level (0 to 1)", "%4.2f", colors.alpha); - telemetry.update(); - - return colors.alpha; - } -} diff --git a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/RobotHardware.java b/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/RobotHardware.java deleted file mode 100644 index b1c8de6..0000000 --- a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/RobotHardware.java +++ /dev/null @@ -1,167 +0,0 @@ -/* Copyright (c) 2022 FIRST. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted (subject to the limitations in the disclaimer below) provided that - * the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list - * of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this - * list of conditions and the following disclaimer in the documentation and/or - * other materials provided with the distribution. - * - * Neither the name of FIRST nor the names of its contributors may be used to endorse or - * promote products derived from this software without specific prior written permission. - * - * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS - * LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.firstinspires.ftc.robotcontroller.external.samples; - -import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode; -import com.qualcomm.robotcore.hardware.DcMotor; -import com.qualcomm.robotcore.hardware.Servo; -import com.qualcomm.robotcore.util.Range; - -/* - * This file works in conjunction with the External Hardware Class sample called: ConceptExternalHardwareClass.java - * Please read the explanations in that Sample about how to use this class definition. - * - * This file defines a Java Class that performs all the setup and configuration for a sample robot's hardware (motors and sensors). - * It assumes three motors (left_drive, right_drive and arm) and two servos (left_hand and right_hand) - * - * This one file/class can be used by ALL of your OpModes without having to cut & paste the code each time. - * - * Where possible, the actual hardware objects are "abstracted" (or hidden) so the OpMode code just makes calls into the class, - * rather than accessing the internal hardware directly. This is why the objects are declared "private". - * - * Use Android Studio to Copy this Class, and Paste it into your team's code folder with *exactly the same name*. - * - * Or... In OnBot Java, add a new file named RobotHardware.java, select this sample, and select Not an OpMode. - * Also add a new OpMode, select the sample ConceptExternalHardwareClass.java, and select TeleOp. - * - */ - -public class RobotHardware { - - /* Declare OpMode members. */ - private LinearOpMode myOpMode = null; // gain access to methods in the calling OpMode. - - // Define Motor and Servo objects (Make them private so they can't be accessed externally) - private DcMotor leftDrive = null; - private DcMotor rightDrive = null; - private DcMotor armMotor = null; - private Servo leftHand = null; - private Servo rightHand = null; - - // Define Drive constants. Make them public so they CAN be used by the calling OpMode - public static final double MID_SERVO = 0.5 ; - public static final double HAND_SPEED = 0.02 ; // sets rate to move servo - public static final double ARM_UP_POWER = 0.45 ; - public static final double ARM_DOWN_POWER = -0.45 ; - - // Define a constructor that allows the OpMode to pass a reference to itself. - public RobotHardware (LinearOpMode opmode) { - myOpMode = opmode; - } - - /** - * Initialize all the robot's hardware. - * This method must be called ONCE when the OpMode is initialized. - *

- * All of the hardware devices are accessed via the hardware map, and initialized. - */ - public void init() { - // Define and Initialize Motors (note: need to use reference to actual OpMode). - leftDrive = myOpMode.hardwareMap.get(DcMotor.class, "left_drive"); - rightDrive = myOpMode.hardwareMap.get(DcMotor.class, "right_drive"); - armMotor = myOpMode.hardwareMap.get(DcMotor.class, "arm"); - - // To drive forward, most robots need the motor on one side to be reversed, because the axles point in opposite directions. - // Pushing the left stick forward MUST make robot go forward. So adjust these two lines based on your first test drive. - // Note: The settings here assume direct drive on left and right wheels. Gear Reduction or 90 Deg drives may require direction flips - leftDrive.setDirection(DcMotor.Direction.REVERSE); - rightDrive.setDirection(DcMotor.Direction.FORWARD); - - // If there are encoders connected, switch to RUN_USING_ENCODER mode for greater accuracy - // leftDrive.setMode(DcMotor.RunMode.RUN_USING_ENCODER); - // rightDrive.setMode(DcMotor.RunMode.RUN_USING_ENCODER); - - // Define and initialize ALL installed servos. - leftHand = myOpMode.hardwareMap.get(Servo.class, "left_hand"); - rightHand = myOpMode.hardwareMap.get(Servo.class, "right_hand"); - leftHand.setPosition(MID_SERVO); - rightHand.setPosition(MID_SERVO); - - myOpMode.telemetry.addData(">", "Hardware Initialized"); - myOpMode.telemetry.update(); - } - - /** - * Calculates the left/right motor powers required to achieve the requested - * robot motions: Drive (Axial motion) and Turn (Yaw motion). - * Then sends these power levels to the motors. - * - * @param Drive Fwd/Rev driving power (-1.0 to 1.0) +ve is forward - * @param Turn Right/Left turning power (-1.0 to 1.0) +ve is CW - */ - public void driveRobot(double Drive, double Turn) { - // Combine drive and turn for blended motion. - double left = Drive + Turn; - double right = Drive - Turn; - - // Scale the values so neither exceed +/- 1.0 - double max = Math.max(Math.abs(left), Math.abs(right)); - if (max > 1.0) - { - left /= max; - right /= max; - } - - // Use existing function to drive both wheels. - setDrivePower(left, right); - } - - /** - * Pass the requested wheel motor powers to the appropriate hardware drive motors. - * - * @param leftWheel Fwd/Rev driving power (-1.0 to 1.0) +ve is forward - * @param rightWheel Fwd/Rev driving power (-1.0 to 1.0) +ve is forward - */ - public void setDrivePower(double leftWheel, double rightWheel) { - // Output the values to the motor drives. - leftDrive.setPower(leftWheel); - rightDrive.setPower(rightWheel); - } - - /** - * Pass the requested arm power to the appropriate hardware drive motor - * - * @param power driving power (-1.0 to 1.0) - */ - public void setArmPower(double power) { - armMotor.setPower(power); - } - - /** - * Send the two hand-servos to opposing (mirrored) positions, based on the passed offset. - * - * @param offset - */ - public void setHandPositions(double offset) { - offset = Range.clip(offset, -0.5, 0.5); - leftHand.setPosition(MID_SERVO + offset); - rightHand.setPosition(MID_SERVO - offset); - } -} diff --git a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/RobotTeleopPOV_Linear.java b/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/RobotTeleopPOV_Linear.java deleted file mode 100644 index af3840d..0000000 --- a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/RobotTeleopPOV_Linear.java +++ /dev/null @@ -1,159 +0,0 @@ -/* Copyright (c) 2017 FIRST. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted (subject to the limitations in the disclaimer below) provided that - * the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list - * of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this - * list of conditions and the following disclaimer in the documentation and/or - * other materials provided with the distribution. - * - * Neither the name of FIRST nor the names of its contributors may be used to endorse or - * promote products derived from this software without specific prior written permission. - * - * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS - * LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.firstinspires.ftc.robotcontroller.external.samples; - -import com.qualcomm.robotcore.eventloop.opmode.Disabled; -import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode; -import com.qualcomm.robotcore.eventloop.opmode.TeleOp; -import com.qualcomm.robotcore.hardware.DcMotor; -import com.qualcomm.robotcore.hardware.Servo; -import com.qualcomm.robotcore.util.Range; - -/* - * This OpMode executes a POV Game style Teleop for a direct drive robot - * The code is structured as a LinearOpMode - * - * In this mode the left stick moves the robot FWD and back, the Right stick turns left and right. - * It raises and lowers the arm using the Gamepad Y and A buttons respectively. - * It also opens and closes the claws slowly using the left and right Bumper buttons. - * - * Use Android Studio to Copy this Class, and Paste it into your team's code folder with a new name. - * Remove or comment out the @Disabled line to add this OpMode to the Driver Station OpMode list - */ - -@TeleOp(name="Robot: Teleop POV", group="Robot") -@Disabled -public class RobotTeleopPOV_Linear extends LinearOpMode { - - /* Declare OpMode members. */ - public DcMotor leftDrive = null; - public DcMotor rightDrive = null; - public DcMotor leftArm = null; - public Servo leftClaw = null; - public Servo rightClaw = null; - - double clawOffset = 0; - - public static final double MID_SERVO = 0.5 ; - public static final double CLAW_SPEED = 0.02 ; // sets rate to move servo - public static final double ARM_UP_POWER = 0.45 ; - public static final double ARM_DOWN_POWER = -0.45 ; - - @Override - public void runOpMode() { - double left; - double right; - double drive; - double turn; - double max; - - // Define and Initialize Motors - leftDrive = hardwareMap.get(DcMotor.class, "left_drive"); - rightDrive = hardwareMap.get(DcMotor.class, "right_drive"); - leftArm = hardwareMap.get(DcMotor.class, "left_arm"); - - // To drive forward, most robots need the motor on one side to be reversed, because the axles point in opposite directions. - // Pushing the left stick forward MUST make robot go forward. So adjust these two lines based on your first test drive. - // Note: The settings here assume direct drive on left and right wheels. Gear Reduction or 90 Deg drives may require direction flips - leftDrive.setDirection(DcMotor.Direction.REVERSE); - rightDrive.setDirection(DcMotor.Direction.FORWARD); - - // If there are encoders connected, switch to RUN_USING_ENCODER mode for greater accuracy - // leftDrive.setMode(DcMotor.RunMode.RUN_USING_ENCODER); - // rightDrive.setMode(DcMotor.RunMode.RUN_USING_ENCODER); - - // Define and initialize ALL installed servos. - leftClaw = hardwareMap.get(Servo.class, "left_hand"); - rightClaw = hardwareMap.get(Servo.class, "right_hand"); - leftClaw.setPosition(MID_SERVO); - rightClaw.setPosition(MID_SERVO); - - // Send telemetry message to signify robot waiting; - telemetry.addData(">", "Robot Ready. Press START."); // - telemetry.update(); - - // Wait for the game to start (driver presses START) - waitForStart(); - - // run until the end of the match (driver presses STOP) - while (opModeIsActive()) { - - // Run wheels in POV mode (note: The joystick goes negative when pushed forward, so negate it) - // In this mode the Left stick moves the robot fwd and back, the Right stick turns left and right. - // This way it's also easy to just drive straight, or just turn. - drive = -gamepad1.left_stick_y; - turn = gamepad1.right_stick_x; - - // Combine drive and turn for blended motion. - left = drive + turn; - right = drive - turn; - - // Normalize the values so neither exceed +/- 1.0 - max = Math.max(Math.abs(left), Math.abs(right)); - if (max > 1.0) - { - left /= max; - right /= max; - } - - // Output the safe vales to the motor drives. - leftDrive.setPower(left); - rightDrive.setPower(right); - - // Use gamepad left & right Bumpers to open and close the claw - if (gamepad1.right_bumper) - clawOffset += CLAW_SPEED; - else if (gamepad1.left_bumper) - clawOffset -= CLAW_SPEED; - - // Move both servos to new position. Assume servos are mirror image of each other. - clawOffset = Range.clip(clawOffset, -0.5, 0.5); - leftClaw.setPosition(MID_SERVO + clawOffset); - rightClaw.setPosition(MID_SERVO - clawOffset); - - // Use gamepad buttons to move arm up (Y) and down (A) - if (gamepad1.y) - leftArm.setPower(ARM_UP_POWER); - else if (gamepad1.a) - leftArm.setPower(ARM_DOWN_POWER); - else - leftArm.setPower(0.0); - - // Send telemetry message to signify robot running; - telemetry.addData("claw", "Offset = %.2f", clawOffset); - telemetry.addData("left", "%.2f", left); - telemetry.addData("right", "%.2f", right); - telemetry.update(); - - // Pace this loop so jaw action is reasonable speed. - sleep(50); - } - } -} diff --git a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/RobotTeleopTank_Iterative.java b/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/RobotTeleopTank_Iterative.java deleted file mode 100644 index a622f27..0000000 --- a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/RobotTeleopTank_Iterative.java +++ /dev/null @@ -1,160 +0,0 @@ -/* Copyright (c) 2017 FIRST. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted (subject to the limitations in the disclaimer below) provided that - * the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list - * of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this - * list of conditions and the following disclaimer in the documentation and/or - * other materials provided with the distribution. - * - * Neither the name of FIRST nor the names of its contributors may be used to endorse or - * promote products derived from this software without specific prior written permission. - * - * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS - * LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.firstinspires.ftc.robotcontroller.external.samples; - -import com.qualcomm.robotcore.eventloop.opmode.Disabled; -import com.qualcomm.robotcore.eventloop.opmode.OpMode; -import com.qualcomm.robotcore.eventloop.opmode.TeleOp; -import com.qualcomm.robotcore.hardware.DcMotor; -import com.qualcomm.robotcore.hardware.Servo; -import com.qualcomm.robotcore.util.Range; - -/* - * This OpMode executes a Tank Drive control TeleOp a direct drive robot - * The code is structured as an Iterative OpMode - * - * In this mode, the left and right joysticks control the left and right motors respectively. - * Pushing a joystick forward will make the attached motor drive forward. - * It raises and lowers the claw using the Gamepad Y and A buttons respectively. - * It also opens and closes the claws slowly using the left and right Bumper buttons. - * - * Use Android Studio to Copy this Class, and Paste it into your team's code folder with a new name. - * Remove or comment out the @Disabled line to add this OpMode to the Driver Station OpMode list - */ - -@TeleOp(name="Robot: Teleop Tank", group="Robot") -@Disabled -public class RobotTeleopTank_Iterative extends OpMode{ - - /* Declare OpMode members. */ - public DcMotor leftDrive = null; - public DcMotor rightDrive = null; - public DcMotor leftArm = null; - public Servo leftClaw = null; - public Servo rightClaw = null; - - double clawOffset = 0; - - public static final double MID_SERVO = 0.5 ; - public static final double CLAW_SPEED = 0.02 ; // sets rate to move servo - public static final double ARM_UP_POWER = 0.50 ; // Run arm motor up at 50% power - public static final double ARM_DOWN_POWER = -0.25 ; // Run arm motor down at -25% power - - /* - * Code to run ONCE when the driver hits INIT - */ - @Override - public void init() { - // Define and Initialize Motors - leftDrive = hardwareMap.get(DcMotor.class, "left_drive"); - rightDrive = hardwareMap.get(DcMotor.class, "right_drive"); - leftArm = hardwareMap.get(DcMotor.class, "left_arm"); - - // To drive forward, most robots need the motor on one side to be reversed, because the axles point in opposite directions. - // Pushing the left and right sticks forward MUST make robot go forward. So adjust these two lines based on your first test drive. - // Note: The settings here assume direct drive on left and right wheels. Gear Reduction or 90 Deg drives may require direction flips - leftDrive.setDirection(DcMotor.Direction.REVERSE); - rightDrive.setDirection(DcMotor.Direction.FORWARD); - - // If there are encoders connected, switch to RUN_USING_ENCODER mode for greater accuracy - // leftDrive.setMode(DcMotor.RunMode.RUN_USING_ENCODER); - // rightDrive.setMode(DcMotor.RunMode.RUN_USING_ENCODER); - - // Define and initialize ALL installed servos. - leftClaw = hardwareMap.get(Servo.class, "left_hand"); - rightClaw = hardwareMap.get(Servo.class, "right_hand"); - leftClaw.setPosition(MID_SERVO); - rightClaw.setPosition(MID_SERVO); - - // Send telemetry message to signify robot waiting; - telemetry.addData(">", "Robot Ready. Press START."); // - } - - /* - * Code to run REPEATEDLY after the driver hits INIT, but before they hit START - */ - @Override - public void init_loop() { - } - - /* - * Code to run ONCE when the driver hits START - */ - @Override - public void start() { - } - - /* - * Code to run REPEATEDLY after the driver hits START but before they hit STOP - */ - @Override - public void loop() { - double left; - double right; - - // Run wheels in tank mode (note: The joystick goes negative when pushed forward, so negate it) - left = -gamepad1.left_stick_y; - right = -gamepad1.right_stick_y; - - leftDrive.setPower(left); - rightDrive.setPower(right); - - // Use gamepad left & right Bumpers to open and close the claw - if (gamepad1.right_bumper) - clawOffset += CLAW_SPEED; - else if (gamepad1.left_bumper) - clawOffset -= CLAW_SPEED; - - // Move both servos to new position. Assume servos are mirror image of each other. - clawOffset = Range.clip(clawOffset, -0.5, 0.5); - leftClaw.setPosition(MID_SERVO + clawOffset); - rightClaw.setPosition(MID_SERVO - clawOffset); - - // Use gamepad buttons to move the arm up (Y) and down (A) - if (gamepad1.y) - leftArm.setPower(ARM_UP_POWER); - else if (gamepad1.a) - leftArm.setPower(ARM_DOWN_POWER); - else - leftArm.setPower(0.0); - - // Send telemetry message to signify robot running; - telemetry.addData("claw", "Offset = %.2f", clawOffset); - telemetry.addData("left", "%.2f", left); - telemetry.addData("right", "%.2f", right); - } - - /* - * Code to run ONCE after the driver hits STOP - */ - @Override - public void stop() { - } -} diff --git a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SampleRevBlinkinLedDriver.java b/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SampleRevBlinkinLedDriver.java deleted file mode 100644 index bcf5b80..0000000 --- a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SampleRevBlinkinLedDriver.java +++ /dev/null @@ -1,163 +0,0 @@ -/* - * Copyright (c) 2018 Craig MacFarlane - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted - * (subject to the limitations in the disclaimer below) provided that the following conditions are - * met: - * - * Redistributions of source code must retain the above copyright notice, this list of conditions - * and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this list of conditions - * and the following disclaimer in the documentation and/or other materials provided with the - * distribution. - * - * Neither the name of Craig MacFarlane nor the names of its contributors may be used to - * endorse or promote products derived from this software without specific prior written permission. - * - * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS LICENSE. THIS - * SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS - * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, - * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF - * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package org.firstinspires.ftc.robotcontroller.external.samples; - -import com.qualcomm.hardware.rev.RevBlinkinLedDriver; -import com.qualcomm.robotcore.eventloop.opmode.Disabled; -import com.qualcomm.robotcore.eventloop.opmode.OpMode; -import com.qualcomm.robotcore.eventloop.opmode.TeleOp; -import org.firstinspires.ftc.robotcore.external.Telemetry; -import org.firstinspires.ftc.robotcore.internal.system.Deadline; - -import java.util.concurrent.TimeUnit; - -/* - * This OpMode demonstrates use of the REV Robotics Blinkin LED Driver. - * AUTO mode cycles through all of the patterns. - * MANUAL mode allows the user to manually change patterns using the - * left and right bumpers of a gamepad. - * - * Configure the driver on a servo port, and name it "blinkin". - * - * Displays the first pattern upon init. - */ -@TeleOp(name="BlinkinExample") -@Disabled -public class SampleRevBlinkinLedDriver extends OpMode { - - /* - * Change the pattern every 10 seconds in AUTO mode. - */ - private final static int LED_PERIOD = 10; - - /* - * Rate limit gamepad button presses to every 500ms. - */ - private final static int GAMEPAD_LOCKOUT = 500; - - RevBlinkinLedDriver blinkinLedDriver; - RevBlinkinLedDriver.BlinkinPattern pattern; - - Telemetry.Item patternName; - Telemetry.Item display; - DisplayKind displayKind; - Deadline ledCycleDeadline; - Deadline gamepadRateLimit; - - protected enum DisplayKind { - MANUAL, - AUTO - } - - @Override - public void init() - { - displayKind = DisplayKind.AUTO; - - blinkinLedDriver = hardwareMap.get(RevBlinkinLedDriver.class, "blinkin"); - pattern = RevBlinkinLedDriver.BlinkinPattern.RAINBOW_RAINBOW_PALETTE; - blinkinLedDriver.setPattern(pattern); - - display = telemetry.addData("Display Kind: ", displayKind.toString()); - patternName = telemetry.addData("Pattern: ", pattern.toString()); - - ledCycleDeadline = new Deadline(LED_PERIOD, TimeUnit.SECONDS); - gamepadRateLimit = new Deadline(GAMEPAD_LOCKOUT, TimeUnit.MILLISECONDS); - } - - @Override - public void loop() - { - handleGamepad(); - - if (displayKind == DisplayKind.AUTO) { - doAutoDisplay(); - } else { - /* - * MANUAL mode: Nothing to do, setting the pattern as a result of a gamepad event. - */ - } - } - - /* - * handleGamepad - * - * Responds to a gamepad button press. Demonstrates rate limiting for - * button presses. If loop() is called every 10ms and and you don't rate - * limit, then any given button press may register as multiple button presses, - * which in this application is problematic. - * - * A: Manual mode, Right bumper displays the next pattern, left bumper displays the previous pattern. - * B: Auto mode, pattern cycles, changing every LED_PERIOD seconds. - */ - protected void handleGamepad() - { - if (!gamepadRateLimit.hasExpired()) { - return; - } - - if (gamepad1.a) { - setDisplayKind(DisplayKind.MANUAL); - gamepadRateLimit.reset(); - } else if (gamepad1.b) { - setDisplayKind(DisplayKind.AUTO); - gamepadRateLimit.reset(); - } else if ((displayKind == DisplayKind.MANUAL) && (gamepad1.left_bumper)) { - pattern = pattern.previous(); - displayPattern(); - gamepadRateLimit.reset(); - } else if ((displayKind == DisplayKind.MANUAL) && (gamepad1.right_bumper)) { - pattern = pattern.next(); - displayPattern(); - gamepadRateLimit.reset(); - } - } - - protected void setDisplayKind(DisplayKind displayKind) - { - this.displayKind = displayKind; - display.setValue(displayKind.toString()); - } - - protected void doAutoDisplay() - { - if (ledCycleDeadline.hasExpired()) { - pattern = pattern.next(); - displayPattern(); - ledCycleDeadline.reset(); - } - } - - protected void displayPattern() - { - blinkinLedDriver.setPattern(pattern); - patternName.setValue(pattern.toString()); - } -} diff --git a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorBNO055IMU.java b/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorBNO055IMU.java deleted file mode 100644 index 405da1e..0000000 --- a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorBNO055IMU.java +++ /dev/null @@ -1,186 +0,0 @@ -/* Copyright (c) 2017 FIRST. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted (subject to the limitations in the disclaimer below) provided that - * the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list - * of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this - * list of conditions and the following disclaimer in the documentation and/or - * other materials provided with the distribution. - * - * Neither the name of FIRST nor the names of its contributors may be used to endorse or - * promote products derived from this software without specific prior written permission. - * - * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS - * LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.firstinspires.ftc.robotcontroller.external.samples; - -import com.qualcomm.hardware.bosch.BNO055IMU; -import com.qualcomm.hardware.bosch.JustLoggingAccelerationIntegrator; -import com.qualcomm.robotcore.eventloop.opmode.Disabled; -import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode; -import com.qualcomm.robotcore.eventloop.opmode.TeleOp; -import org.firstinspires.ftc.robotcore.external.Func; -import org.firstinspires.ftc.robotcore.external.navigation.Acceleration; -import org.firstinspires.ftc.robotcore.external.navigation.AngleUnit; -import org.firstinspires.ftc.robotcore.external.navigation.AxesOrder; -import org.firstinspires.ftc.robotcore.external.navigation.AxesReference; -import org.firstinspires.ftc.robotcore.external.navigation.Orientation; -import org.firstinspires.ftc.robotcore.external.navigation.Position; -import org.firstinspires.ftc.robotcore.external.navigation.Velocity; - -import java.util.Locale; - -/* - * This OpMode gives a short demo on how to use the BNO055 Inertial Motion Unit (IMU) from AdaFruit. - * - * Note: this is a Legacy example that will not work with newer Control/Expansion Hubs that use a different IMU - * Please use the new SensorIMUOrthogonal or SensorIMUNonOrthogonal samples for a more universal IMU interface. - * - * Use Android Studio to Copy this Class, and Paste it into your team's code folder with a new name. - * Remove or comment out the @Disabled line to add this OpMode to the Driver Station OpMode list - * - * @see Adafruit IMU - */ -@TeleOp(name = "Sensor: BNO055 IMU", group = "Sensor") -@Disabled // Comment this out to add to the OpMode list -public class SensorBNO055IMU extends LinearOpMode - { - //---------------------------------------------------------------------------------------------- - // State - //---------------------------------------------------------------------------------------------- - - // The IMU sensor object - BNO055IMU imu; - - // State used for updating telemetry - Orientation angles; - Acceleration gravity; - - //---------------------------------------------------------------------------------------------- - // Main logic - //---------------------------------------------------------------------------------------------- - - @Override public void runOpMode() { - - // Set up the parameters with which we will use our IMU. Note that integration - // algorithm here just reports accelerations to the logcat log; it doesn't actually - // provide positional information. - BNO055IMU.Parameters parameters = new BNO055IMU.Parameters(); - parameters.angleUnit = BNO055IMU.AngleUnit.DEGREES; - parameters.accelUnit = BNO055IMU.AccelUnit.METERS_PERSEC_PERSEC; - parameters.calibrationDataFile = "BNO055IMUCalibration.json"; // see the calibration sample OpMode - parameters.loggingEnabled = true; - parameters.loggingTag = "IMU"; - parameters.accelerationIntegrationAlgorithm = new JustLoggingAccelerationIntegrator(); - - // Retrieve and initialize the IMU. We expect the IMU to be attached to an I2C port - // on a Core Device Interface Module, configured to be a sensor of type "AdaFruit IMU", - // and named "imu". - imu = hardwareMap.get(BNO055IMU.class, "imu"); - imu.initialize(parameters); - - // Set up our telemetry dashboard - composeTelemetry(); - - // Wait until we're told to go - waitForStart(); - - // Start the logging of measured acceleration - imu.startAccelerationIntegration(new Position(), new Velocity(), 1000); - - // Loop and update the dashboard - while (opModeIsActive()) { - telemetry.update(); - } - } - - //---------------------------------------------------------------------------------------------- - // Telemetry Configuration - //---------------------------------------------------------------------------------------------- - - void composeTelemetry() { - - // At the beginning of each telemetry update, grab a bunch of data - // from the IMU that we will then display in separate lines. - telemetry.addAction(new Runnable() { @Override public void run() - { - // Acquiring the angles is relatively expensive; we don't want - // to do that in each of the three items that need that info, as that's - // three times the necessary expense. - angles = imu.getAngularOrientation(AxesReference.INTRINSIC, AxesOrder.ZYX, AngleUnit.DEGREES); - gravity = imu.getGravity(); - } - }); - - telemetry.addLine() - .addData("status", new Func() { - @Override public String value() { - return imu.getSystemStatus().toShortString(); - } - }) - .addData("calib", new Func() { - @Override public String value() { - return imu.getCalibrationStatus().toString(); - } - }); - - telemetry.addLine() - .addData("heading", new Func() { - @Override public String value() { - return formatAngle(angles.angleUnit, angles.firstAngle); - } - }) - .addData("roll", new Func() { - @Override public String value() { - return formatAngle(angles.angleUnit, angles.secondAngle); - } - }) - .addData("pitch", new Func() { - @Override public String value() { - return formatAngle(angles.angleUnit, angles.thirdAngle); - } - }); - - telemetry.addLine() - .addData("grvty", new Func() { - @Override public String value() { - return gravity.toString(); - } - }) - .addData("mag", new Func() { - @Override public String value() { - return String.format(Locale.getDefault(), "%.3f", - Math.sqrt(gravity.xAccel*gravity.xAccel - + gravity.yAccel*gravity.yAccel - + gravity.zAccel*gravity.zAccel)); - } - }); - } - - //---------------------------------------------------------------------------------------------- - // Formatting - //---------------------------------------------------------------------------------------------- - - String formatAngle(AngleUnit angleUnit, double angle) { - return formatDegrees(AngleUnit.DEGREES.fromUnit(angleUnit, angle)); - } - - String formatDegrees(double degrees){ - return String.format(Locale.getDefault(), "%.1f", AngleUnit.DEGREES.normalize(degrees)); - } -} diff --git a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorBNO055IMUCalibration.java b/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorBNO055IMUCalibration.java deleted file mode 100644 index 93f1789..0000000 --- a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorBNO055IMUCalibration.java +++ /dev/null @@ -1,230 +0,0 @@ -/* Copyright (c) 2017 FIRST. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted (subject to the limitations in the disclaimer below) provided that - * the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list - * of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this - * list of conditions and the following disclaimer in the documentation and/or - * other materials provided with the distribution. - * - * Neither the name of FIRST nor the names of its contributors may be used to endorse or - * promote products derived from this software without specific prior written permission. - * - * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS - * LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.firstinspires.ftc.robotcontroller.external.samples; - -import com.qualcomm.hardware.bosch.BNO055IMU; -import com.qualcomm.robotcore.eventloop.opmode.Disabled; -import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode; -import com.qualcomm.robotcore.eventloop.opmode.TeleOp; -import com.qualcomm.robotcore.util.ReadWriteFile; -import org.firstinspires.ftc.robotcore.external.Func; -import org.firstinspires.ftc.robotcore.external.navigation.AngleUnit; -import org.firstinspires.ftc.robotcore.external.navigation.AxesOrder; -import org.firstinspires.ftc.robotcore.external.navigation.AxesReference; -import org.firstinspires.ftc.robotcore.external.navigation.Orientation; -import org.firstinspires.ftc.robotcore.internal.system.AppUtil; - -import java.io.File; -import java.util.Locale; - -/* - * This OpMode calibrates a BNO055 IMU per - * "Section 3.11 Calibration" of the BNO055 specification. - * - * Note: this is a Legacy example that will not work with newer Control/Expansion Hubs that use a different IMU - * Please use the new SensorIMUOrthogonal or SensorIMUNonOrthogonal samples for a more universal IMU interface. - * - * Manual calibration of the IMU is definitely NOT necessary: except for the magnetometer (which is not used by the - * default "IMU" SensorMode), the BNO055 is internally self-calibrating and thus can be very successfully used without - * manual intervention. That said, performing a one-time calibration, saving the results persistently, then loading them - * again at each run can help reduce the time that automatic calibration requires. - * - * This summary of the calibration process from Intel is informative: - * http://iotdk.intel.com/docs/master/upm/classupm_1_1_b_n_o055.html - * - * "This device requires calibration in order to operate accurately. [...] Calibration data is - * lost on a power cycle. See one of the examples for a description of how to calibrate the device, - * but in essence: - * - * There is a calibration status register available [...] that returns the calibration status - * of the accelerometer (ACC), magnetometer (MAG), gyroscope (GYR), and overall system (SYS). - * Each of these values range from 0 (uncalibrated) to 3 (fully calibrated). Calibration [ideally] - * involves certain motions to get all 4 values at 3. The motions are as follows (though see the - * datasheet for more information): - * - * 1. GYR: Simply let the sensor sit flat for a few seconds. - * 2. ACC: Move the sensor in various positions. Start flat, then rotate slowly by 45 - * degrees, hold for a few seconds, then continue rotating another 45 degrees and - * hold, etc. 6 or more movements of this type may be required. You can move through - * any axis you desire, but make sure that the device is lying at least once - * perpendicular to the x, y, and z axis. - * 3. MAG: Move slowly in a figure 8 pattern in the air, until the calibration values reaches 3. - * 4. SYS: This will usually reach 3 when the other items have also reached 3. If not, continue - * slowly moving the device though various axes until it does." - * - * To calibrate the IMU, run this sample OpMode with a gamepad attached to the driver station. - * Once the IMU has reached sufficient calibration as reported on telemetry, press the 'A' - * button on the gamepad to write the calibration to a file. That file can then be indicated - * later when running an OpMode which uses the IMU. - * - * Note: if your intended uses of the IMU do not include use of all its sensors (for example, - * you might not use the magnetometer), then it makes little sense for you to wait for full - * calibration of the sensors you are not using before saving the calibration data. Indeed, - * it appears that in a SensorMode that doesn't use the magnetometer (for example), the - * magnetometer cannot actually be calibrated. - * - * References: - * The AdafruitBNO055IMU Javadoc - * The BNO055IMU.Parameters.calibrationDataFile Javadoc - * The BNO055 product page: https://www.bosch-sensortec.com/bst/products/all_products/bno055 - * The BNO055 datasheet: https://www.bosch-sensortec.com/media/boschsensortec/downloads/datasheets/bst-bno055-ds000.pdf - */ -@TeleOp(name = "Sensor: BNO055 IMU Calibration", group = "Sensor") -@Disabled // Uncomment this to add to the OpMode list -public class SensorBNO055IMUCalibration extends LinearOpMode - { - //---------------------------------------------------------------------------------------------- - // State - //---------------------------------------------------------------------------------------------- - - // Our sensors, motors, and other devices go here, along with other long term state - BNO055IMU imu; - - // State used for updating telemetry - Orientation angles; - - //---------------------------------------------------------------------------------------------- - // Main logic - //---------------------------------------------------------------------------------------------- - - @Override public void runOpMode() { - - telemetry.log().setCapacity(12); - telemetry.log().add(""); - telemetry.log().add("Please refer to the calibration instructions"); - telemetry.log().add("contained in the Adafruit IMU calibration"); - telemetry.log().add("sample OpMode."); - telemetry.log().add(""); - telemetry.log().add("When sufficient calibration has been reached,"); - telemetry.log().add("press the 'A' button to write the current"); - telemetry.log().add("calibration data to a file."); - telemetry.log().add(""); - - // We are expecting the IMU to be attached to an I2C port on a Core Device Interface Module and named "imu". - BNO055IMU.Parameters parameters = new BNO055IMU.Parameters(); - parameters.loggingEnabled = true; - parameters.loggingTag = "IMU"; - imu = hardwareMap.get(BNO055IMU.class, "imu"); - imu.initialize(parameters); - - composeTelemetry(); - telemetry.log().add("Waiting for start..."); - - // Wait until we're told to go - while (!isStarted()) { - telemetry.update(); - idle(); - } - - telemetry.log().add("...started..."); - - while (opModeIsActive()) { - - if (gamepad1.a) { - - // Get the calibration data - BNO055IMU.CalibrationData calibrationData = imu.readCalibrationData(); - - // Save the calibration data to a file. You can choose whatever file - // name you wish here, but you'll want to indicate the same file name - // when you initialize the IMU in an OpMode in which it is used. If you - // have more than one IMU on your robot, you'll of course want to use - // different configuration file names for each. - String filename = "AdafruitIMUCalibration.json"; - File file = AppUtil.getInstance().getSettingsFile(filename); - ReadWriteFile.writeFile(file, calibrationData.serialize()); - telemetry.log().add("saved to '%s'", filename); - - // Wait for the button to be released - while (gamepad1.a) { - telemetry.update(); - idle(); - } - } - - telemetry.update(); - } - } - - void composeTelemetry() { - - // At the beginning of each telemetry update, grab a bunch of data - // from the IMU that we will then display in separate lines. - telemetry.addAction(new Runnable() { @Override public void run() - { - // Acquiring the angles is relatively expensive; we don't want - // to do that in each of the three items that need that info, as that's - // three times the necessary expense. - angles = imu.getAngularOrientation(AxesReference.INTRINSIC, AxesOrder.ZYX, AngleUnit.DEGREES); - } - }); - - telemetry.addLine() - .addData("status", new Func() { - @Override public String value() { - return imu.getSystemStatus().toShortString(); - } - }) - .addData("calib", new Func() { - @Override public String value() { - return imu.getCalibrationStatus().toString(); - } - }); - - telemetry.addLine() - .addData("heading", new Func() { - @Override public String value() { - return formatAngle(angles.angleUnit, angles.firstAngle); - } - }) - .addData("roll", new Func() { - @Override public String value() { - return formatAngle(angles.angleUnit, angles.secondAngle); - } - }) - .addData("pitch", new Func() { - @Override public String value() { - return formatAngle(angles.angleUnit, angles.thirdAngle); - } - }); - } - - //---------------------------------------------------------------------------------------------- - // Formatting - //---------------------------------------------------------------------------------------------- - - String formatAngle(AngleUnit angleUnit, double angle) { - return formatDegrees(AngleUnit.DEGREES.fromUnit(angleUnit, angle)); - } - - String formatDegrees(double degrees){ - return String.format(Locale.getDefault(), "%.1f", AngleUnit.DEGREES.normalize(degrees)); - } -} diff --git a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorColor.java b/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorColor.java deleted file mode 100644 index 7546c9d..0000000 --- a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorColor.java +++ /dev/null @@ -1,221 +0,0 @@ -/* Copyright (c) 2017-2020 FIRST. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted (subject to the limitations in the disclaimer below) provided that - * the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list - * of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this - * list of conditions and the following disclaimer in the documentation and/or - * other materials provided with the distribution. - * - * Neither the name of FIRST nor the names of its contributors may be used to endorse or - * promote products derived from this software without specific prior written permission. - * - * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS - * LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.firstinspires.ftc.robotcontroller.external.samples; - -import android.app.Activity; -import android.graphics.Color; -import android.view.View; -import com.qualcomm.robotcore.eventloop.opmode.Disabled; -import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode; -import com.qualcomm.robotcore.eventloop.opmode.TeleOp; -import com.qualcomm.robotcore.hardware.DistanceSensor; -import com.qualcomm.robotcore.hardware.NormalizedColorSensor; -import com.qualcomm.robotcore.hardware.NormalizedRGBA; -import com.qualcomm.robotcore.hardware.SwitchableLight; -import org.firstinspires.ftc.robotcore.external.navigation.DistanceUnit; - -/* - * This OpMode shows how to use a color sensor in a generic - * way, regardless of which particular make or model of color sensor is used. The OpMode - * assumes that the color sensor is configured with a name of "sensor_color". - * - * There will be some variation in the values measured depending on the specific sensor you are using. - * - * You can increase the gain (a multiplier to make the sensor report higher values) by holding down - * the A button on the gamepad, and decrease the gain by holding down the B button on the gamepad. - * - * If the color sensor has a light which is controllable from software, you can use the X button on - * the gamepad to toggle the light on and off. The REV sensors don't support this, but instead have - * a physical switch on them to turn the light on and off, beginning with REV Color Sensor V2. - * - * If the color sensor also supports short-range distance measurements (usually via an infrared - * proximity sensor), the reported distance will be written to telemetry. As of September 2020, - * the only color sensors that support this are the ones from REV Robotics. These infrared proximity - * sensor measurements are only useful at very small distances, and are sensitive to ambient light - * and surface reflectivity. You should use a different sensor if you need precise distance measurements. - * - * Use Android Studio to Copy this Class, and Paste it into your team's code folder with a new name. - * Remove or comment out the @Disabled line to add this OpMode to the Driver Station OpMode list - */ -@TeleOp(name = "Sensor: Color", group = "Sensor") -@Disabled -public class SensorColor extends LinearOpMode { - - /** The colorSensor field will contain a reference to our color sensor hardware object */ - NormalizedColorSensor colorSensor; - - /** The relativeLayout field is used to aid in providing interesting visual feedback - * in this sample application; you probably *don't* need this when you use a color sensor on your - * robot. Note that you won't see anything change on the Driver Station, only on the Robot Controller. */ - View relativeLayout; - - /* - * The runOpMode() method is the root of this OpMode, as it is in all LinearOpModes. - * Our implementation here, though is a bit unusual: we've decided to put all the actual work - * in the runSample() method rather than directly in runOpMode() itself. The reason we do that is - * that in this sample we're changing the background color of the robot controller screen as the - * OpMode runs, and we want to be able to *guarantee* that we restore it to something reasonable - * and palatable when the OpMode ends. The simplest way to do that is to use a try...finally - * block around the main, core logic, and an easy way to make that all clear was to separate - * the former from the latter in separate methods. - */ - @Override public void runOpMode() { - - // Get a reference to the RelativeLayout so we can later change the background - // color of the Robot Controller app to match the hue detected by the RGB sensor. - int relativeLayoutId = hardwareMap.appContext.getResources().getIdentifier("RelativeLayout", "id", hardwareMap.appContext.getPackageName()); - relativeLayout = ((Activity) hardwareMap.appContext).findViewById(relativeLayoutId); - - try { - runSample(); // actually execute the sample - } finally { - // On the way out, *guarantee* that the background is reasonable. It doesn't actually start off - // as pure white, but it's too much work to dig out what actually was used, and this is good - // enough to at least make the screen reasonable again. - // Set the panel back to the default color - relativeLayout.post(new Runnable() { - public void run() { - relativeLayout.setBackgroundColor(Color.WHITE); - } - }); - } - } - - protected void runSample() { - // You can give the sensor a gain value, will be multiplied by the sensor's raw value before the - // normalized color values are calculated. Color sensors (especially the REV Color Sensor V3) - // can give very low values (depending on the lighting conditions), which only use a small part - // of the 0-1 range that is available for the red, green, and blue values. In brighter conditions, - // you should use a smaller gain than in dark conditions. If your gain is too high, all of the - // colors will report at or near 1, and you won't be able to determine what color you are - // actually looking at. For this reason, it's better to err on the side of a lower gain - // (but always greater than or equal to 1). - float gain = 2; - - // Once per loop, we will update this hsvValues array. The first element (0) will contain the - // hue, the second element (1) will contain the saturation, and the third element (2) will - // contain the value. See http://web.archive.org/web/20190311170843/https://infohost.nmt.edu/tcc/help/pubs/colortheory/web/hsv.html - // for an explanation of HSV color. - final float[] hsvValues = new float[3]; - - // xButtonPreviouslyPressed and xButtonCurrentlyPressed keep track of the previous and current - // state of the X button on the gamepad - boolean xButtonPreviouslyPressed = false; - boolean xButtonCurrentlyPressed = false; - - // Get a reference to our sensor object. It's recommended to use NormalizedColorSensor over - // ColorSensor, because NormalizedColorSensor consistently gives values between 0 and 1, while - // the values you get from ColorSensor are dependent on the specific sensor you're using. - colorSensor = hardwareMap.get(NormalizedColorSensor.class, "sensor_color"); - - // If possible, turn the light on in the beginning (it might already be on anyway, - // we just make sure it is if we can). - if (colorSensor instanceof SwitchableLight) { - ((SwitchableLight)colorSensor).enableLight(true); - } - - // Wait for the start button to be pressed. - waitForStart(); - - // Loop until we are asked to stop - while (opModeIsActive()) { - // Explain basic gain information via telemetry - telemetry.addLine("Hold the A button on gamepad 1 to increase gain, or B to decrease it.\n"); - telemetry.addLine("Higher gain values mean that the sensor will report larger numbers for Red, Green, and Blue, and Value\n"); - - // Update the gain value if either of the A or B gamepad buttons is being held - if (gamepad1.a) { - // Only increase the gain by a small amount, since this loop will occur multiple times per second. - gain += 0.005; - } else if (gamepad1.b && gain > 1) { // A gain of less than 1 will make the values smaller, which is not helpful. - gain -= 0.005; - } - - // Show the gain value via telemetry - telemetry.addData("Gain", gain); - - // Tell the sensor our desired gain value (normally you would do this during initialization, - // not during the loop) - colorSensor.setGain(gain); - - // Check the status of the X button on the gamepad - xButtonCurrentlyPressed = gamepad1.x; - - // If the button state is different than what it was, then act - if (xButtonCurrentlyPressed != xButtonPreviouslyPressed) { - // If the button is (now) down, then toggle the light - if (xButtonCurrentlyPressed) { - if (colorSensor instanceof SwitchableLight) { - SwitchableLight light = (SwitchableLight)colorSensor; - light.enableLight(!light.isLightOn()); - } - } - } - xButtonPreviouslyPressed = xButtonCurrentlyPressed; - - // Get the normalized colors from the sensor - NormalizedRGBA colors = colorSensor.getNormalizedColors(); - - /* Use telemetry to display feedback on the driver station. We show the red, green, and blue - * normalized values from the sensor (in the range of 0 to 1), as well as the equivalent - * HSV (hue, saturation and value) values. See http://web.archive.org/web/20190311170843/https://infohost.nmt.edu/tcc/help/pubs/colortheory/web/hsv.html - * for an explanation of HSV color. */ - - // Update the hsvValues array by passing it to Color.colorToHSV() - Color.colorToHSV(colors.toColor(), hsvValues); - - telemetry.addLine() - .addData("Red", "%.3f", colors.red) - .addData("Green", "%.3f", colors.green) - .addData("Blue", "%.3f", colors.blue); - telemetry.addLine() - .addData("Hue", "%.3f", hsvValues[0]) - .addData("Saturation", "%.3f", hsvValues[1]) - .addData("Value", "%.3f", hsvValues[2]); - telemetry.addData("Alpha", "%.3f", colors.alpha); - - /* If this color sensor also has a distance sensor, display the measured distance. - * Note that the reported distance is only useful at very close range, and is impacted by - * ambient light and surface reflectivity. */ - if (colorSensor instanceof DistanceSensor) { - telemetry.addData("Distance (cm)", "%.3f", ((DistanceSensor) colorSensor).getDistance(DistanceUnit.CM)); - } - - telemetry.update(); - - // Change the Robot Controller's background color to match the color detected by the color sensor. - relativeLayout.post(new Runnable() { - public void run() { - relativeLayout.setBackgroundColor(Color.HSVToColor(hsvValues)); - } - }); - } - } -} diff --git a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorDigitalTouch.java b/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorDigitalTouch.java deleted file mode 100644 index 44c3ca9..0000000 --- a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorDigitalTouch.java +++ /dev/null @@ -1,78 +0,0 @@ -/* Copyright (c) 2024 FIRST. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted (subject to the limitations in the disclaimer below) provided that - * the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list - * of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this - * list of conditions and the following disclaimer in the documentation and/or - * other materials provided with the distribution. - * - * Neither the name of FIRST nor the names of its contributors may be used to endorse or - * promote products derived from this software without specific prior written permission. - * - * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS - * LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.firstinspires.ftc.robotcontroller.external.samples; - -import com.qualcomm.robotcore.eventloop.opmode.Disabled; -import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode; -import com.qualcomm.robotcore.eventloop.opmode.TeleOp; -import com.qualcomm.robotcore.hardware.DigitalChannel; - -/* - * This OpMode demonstrates how to use a digital channel. - * - * The OpMode assumes that the digital channel is configured with a name of "digitalTouch". - * - * Use Android Studio to Copy this Class, and Paste it into your team's code folder with a new name. - * Remove or comment out the @Disabled line to add this OpMode to the Driver Station OpMode list. - */ -@TeleOp(name = "Sensor: digital channel", group = "Sensor") -@Disabled -public class SensorDigitalTouch extends LinearOpMode { - DigitalChannel digitalTouch; // Digital channel Object - - @Override - public void runOpMode() { - - // get a reference to our touchSensor object. - digitalTouch = hardwareMap.get(DigitalChannel.class, "digitalTouch"); - - digitalTouch.setMode(DigitalChannel.Mode.INPUT); - telemetry.addData("DigitalTouchSensorExample", "Press start to continue..."); - telemetry.update(); - - // wait for the start button to be pressed. - waitForStart(); - - // while the OpMode is active, loop and read the digital channel. - // Note we use opModeIsActive() as our loop condition because it is an interruptible method. - while (opModeIsActive()) { - - // button is pressed if value returned is LOW or false. - // send the info back to driver station using telemetry function. - if (digitalTouch.getState() == false) { - telemetry.addData("Button", "PRESSED"); - } else { - telemetry.addData("Button", "NOT PRESSED"); - } - - telemetry.update(); - } - } -} diff --git a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorHuskyLens.java b/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorHuskyLens.java deleted file mode 100644 index af7ca55..0000000 --- a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorHuskyLens.java +++ /dev/null @@ -1,160 +0,0 @@ -/* -Copyright (c) 2023 FIRST - -All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted (subject to the limitations in the disclaimer below) provided that -the following conditions are met: - -Redistributions of source code must retain the above copyright notice, this list -of conditions and the following disclaimer. - -Redistributions in binary form must reproduce the above copyright notice, this -list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. - -Neither the name of FIRST nor the names of its contributors may be used to -endorse or promote products derived from this software without specific prior -written permission. - -NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS -LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR -TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF -THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ -package org.firstinspires.ftc.robotcontroller.external.samples; - -import com.qualcomm.hardware.dfrobot.HuskyLens; -import com.qualcomm.robotcore.eventloop.opmode.Disabled; -import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode; -import com.qualcomm.robotcore.eventloop.opmode.TeleOp; - -import org.firstinspires.ftc.robotcore.internal.system.Deadline; - -import java.util.concurrent.TimeUnit; - -/* - * This OpMode illustrates how to use the DFRobot HuskyLens. - * - * The HuskyLens is a Vision Sensor with a built-in object detection model. It can - * detect a number of predefined objects and AprilTags in the 36h11 family, can - * recognize colors, and can be trained to detect custom objects. See this website for - * documentation: https://wiki.dfrobot.com/HUSKYLENS_V1.0_SKU_SEN0305_SEN0336 - * - * For detailed instructions on how a HuskyLens is used in FTC, please see this tutorial: - * https://ftc-docs.firstinspires.org/en/latest/devices/huskylens/huskylens.html - * - * This sample illustrates how to detect AprilTags, but can be used to detect other types - * of objects by changing the algorithm. It assumes that the HuskyLens is configured with - * a name of "huskylens". - * - * Use Android Studio to Copy this Class, and Paste it into your team's code folder with a new name. - * Remove or comment out the @Disabled line to add this OpMode to the Driver Station OpMode list - */ -@TeleOp(name = "Sensor: HuskyLens", group = "Sensor") -@Disabled -public class SensorHuskyLens extends LinearOpMode { - - private final int READ_PERIOD = 1; - - private HuskyLens huskyLens; - - @Override - public void runOpMode() - { - huskyLens = hardwareMap.get(HuskyLens.class, "huskylens"); - - /* - * This sample rate limits the reads solely to allow a user time to observe - * what is happening on the Driver Station telemetry. Typical applications - * would not likely rate limit. - */ - Deadline rateLimit = new Deadline(READ_PERIOD, TimeUnit.SECONDS); - - /* - * Immediately expire so that the first time through we'll do the read. - */ - rateLimit.expire(); - - /* - * Basic check to see if the device is alive and communicating. This is not - * technically necessary here as the HuskyLens class does this in its - * doInitialization() method which is called when the device is pulled out of - * the hardware map. However, sometimes it's unclear why a device reports as - * failing on initialization. In the case of this device, it's because the - * call to knock() failed. - */ - if (!huskyLens.knock()) { - telemetry.addData(">>", "Problem communicating with " + huskyLens.getDeviceName()); - } else { - telemetry.addData(">>", "Press start to continue"); - } - - /* - * The device uses the concept of an algorithm to determine what types of - * objects it will look for and/or what mode it is in. The algorithm may be - * selected using the scroll wheel on the device, or via software as shown in - * the call to selectAlgorithm(). - * - * The SDK itself does not assume that the user wants a particular algorithm on - * startup, and hence does not set an algorithm. - * - * Users, should, in general, explicitly choose the algorithm they want to use - * within the OpMode by calling selectAlgorithm() and passing it one of the values - * found in the enumeration HuskyLens.Algorithm. - * - * Other algorithm choices for FTC might be: OBJECT_RECOGNITION, COLOR_RECOGNITION or OBJECT_CLASSIFICATION. - */ - huskyLens.selectAlgorithm(HuskyLens.Algorithm.TAG_RECOGNITION); - - telemetry.update(); - waitForStart(); - - /* - * Looking for AprilTags per the call to selectAlgorithm() above. A handy grid - * for testing may be found at https://wiki.dfrobot.com/HUSKYLENS_V1.0_SKU_SEN0305_SEN0336#target_20. - * - * Note again that the device only recognizes the 36h11 family of tags out of the box. - */ - while(opModeIsActive()) { - if (!rateLimit.hasExpired()) { - continue; - } - rateLimit.reset(); - - /* - * All algorithms, except for LINE_TRACKING, return a list of Blocks where a - * Block represents the outline of a recognized object along with its ID number. - * ID numbers allow you to identify what the device saw. See the HuskyLens documentation - * referenced in the header comment above for more information on IDs and how to - * assign them to objects. - * - * Returns an empty array if no objects are seen. - */ - HuskyLens.Block[] blocks = huskyLens.blocks(); - telemetry.addData("Block count", blocks.length); - for (int i = 0; i < blocks.length; i++) { - telemetry.addData("Block", blocks[i].toString()); - /* - * Here inside the FOR loop, you could save or evaluate specific info for the currently recognized Bounding Box: - * - blocks[i].width and blocks[i].height (size of box, in pixels) - * - blocks[i].left and blocks[i].top (edges of box) - * - blocks[i].x and blocks[i].y (center location) - * - blocks[i].id (Color ID) - * - * These values have Java type int (integer). - */ - } - - telemetry.update(); - } - } -} \ No newline at end of file diff --git a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorIMUNonOrthogonal.java b/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorIMUNonOrthogonal.java deleted file mode 100644 index 70bc8d4..0000000 --- a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorIMUNonOrthogonal.java +++ /dev/null @@ -1,184 +0,0 @@ -/* Copyright (c) 2022 FIRST. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted (subject to the limitations in the disclaimer below) provided that - * the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list - * of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this - * list of conditions and the following disclaimer in the documentation and/or - * other materials provided with the distribution. - * - * Neither the name of FIRST nor the names of its contributors may be used to endorse or - * promote products derived from this software without specific prior written permission. - * - * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS - * LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.firstinspires.ftc.robotcontroller.external.samples; - -import com.qualcomm.hardware.rev.RevHubOrientationOnRobot; -import com.qualcomm.robotcore.eventloop.opmode.Disabled; -import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode; -import com.qualcomm.robotcore.eventloop.opmode.TeleOp; -import com.qualcomm.robotcore.hardware.IMU; -import org.firstinspires.ftc.robotcore.external.navigation.AngleUnit; -import org.firstinspires.ftc.robotcore.external.navigation.AngularVelocity; -import org.firstinspires.ftc.robotcore.external.navigation.Orientation; -import org.firstinspires.ftc.robotcore.external.navigation.YawPitchRollAngles; - -import static com.qualcomm.hardware.rev.RevHubOrientationOnRobot.xyzOrientation; - -/* - * This OpMode shows how to use the new universal IMU interface. This - * interface may be used with the BNO055 IMU or the BHI260 IMU. It assumes that an IMU is configured - * on the robot with the name "imu". - * - * The sample will display the current Yaw, Pitch and Roll of the robot.
- * With the correct orientation parameters selected, pitch/roll/yaw should act as follows: - * Pitch value should INCREASE as the robot is tipped UP at the front. (Rotation about X) - * Roll value should INCREASE as the robot is tipped UP at the left side. (Rotation about Y) - * Yaw value should INCREASE as the robot is rotated Counter Clockwise. (Rotation about Z) - * - * The yaw can be reset (to zero) by pressing the Y button on the gamepad (Triangle on a PS4 controller) - * - * This specific sample DOES NOT assume that the Hub is mounted on one of the three orthogonal - * planes (X/Y, X/Z or Y/Z) OR that the Hub has only been rotated in a range of 90 degree increments. - * - * Note: if your Hub is mounted Orthogonally (on a orthogonal surface, angled at some multiple of - * 90 Degrees) then you should use the simpler SensorIMUOrthogonal sample in this folder. - * - * But... If your Hub is mounted Non-Orthogonally, you must specify one or more rotational angles - * that transform a "Default" Hub orientation into your desired orientation. That is what is - * illustrated here. - * - * Use Android Studio to Copy this Class, and Paste it into your team's code folder with a new name. - * Remove or comment out the @Disabled line to add this OpMode to the Driver Station OpMode list. - * - * Finally, edit this OpMode to use at least one angle around an axis to orient your Hub. - */ -@TeleOp(name = "Sensor: IMU Non-Orthogonal", group = "Sensor") -@Disabled // Comment this out to add to the OpMode list -public class SensorIMUNonOrthogonal extends LinearOpMode -{ - // The IMU sensor object - IMU imu; - - //---------------------------------------------------------------------------------------------- - // Main logic - //---------------------------------------------------------------------------------------------- - - @Override public void runOpMode() throws InterruptedException { - - // Retrieve and initialize the IMU. - // This sample expects the IMU to be in a REV Hub and named "imu". - imu = hardwareMap.get(IMU.class, "imu"); - - /* Define how the hub is mounted to the robot to get the correct Yaw, Pitch and Roll values. - * - * You can apply up to three axis rotations to orient your Hub according to how it's mounted on the robot. - * - * The starting point for these rotations is the "Default" Hub orientation, which is: - * 1) Hub laying flat on a horizontal surface, with the Printed Logo facing UP - * 2) Rotated such that the USB ports are facing forward on the robot. - * - * If you are using a REV External IMU, the "Default" orientation is the same as for a REV Hub, but instead of - * the USB ports facing forward, the I2C port faces forward. - * - * The order that the rotations are performed matters, so this sample shows doing them in the order X, Y, then Z. - * For specifying non-orthogonal hub mounting orientations, we must temporarily use axes - * defined relative to the Hub itself, instead of the usual Robot Coordinate System axes - * used for the results the IMU gives us. In the starting orientation, the Hub axes are - * aligned with the Robot Coordinate System: - * - * X Axis: Starting at Center of Hub, pointing out towards I2C connectors - * Y Axis: Starting at Center of Hub, pointing out towards USB connectors - * Z Axis: Starting at Center of Hub, pointing Up through LOGO - * - * Positive rotation is defined by right-hand rule with thumb pointing in +ve direction on axis. - * - * Some examples. - * - * ---------------------------------------------------------------------------------------------------------------------------------- - * Example A) Assume that the hub is mounted on a sloped plate at the back of the robot, with the USB ports coming out the top of the hub. - * The plate is tilted UP 60 degrees from horizontal. - * - * To get the "Default" hub into this configuration you would just need a single rotation. - * 1) Rotate the Hub +60 degrees around the X axis to tilt up the front edge. - * 2) No rotation around the Y or Z axes. - * - * So the X,Y,Z rotations would be 60,0,0 - * - * ---------------------------------------------------------------------------------------------------------------------------------- - * Example B) Assume that the hub is laying flat on the chassis, but it has been twisted 30 degrees towards the right front wheel to make - * the USB cable accessible. - * - * To get the "Default" hub into this configuration you would just need a single rotation, but around a different axis. - * 1) No rotation around the X or Y axes. - * 2) Rotate the Hub -30 degrees (Clockwise) around the Z axis, since a positive angle would be Counter Clockwise. - * - * So the X,Y,Z rotations would be 0,0,-30 - * - * ---------------------------------------------------------------------------------------------------------------------------------- - * Example C) Assume that the hub is mounted on a vertical plate on the right side of the robot, with the Logo facing out, and the - * Hub rotated so that the USB ports are facing down 30 degrees towards the back wheels of the robot. - * - * To get the "Default" hub into this configuration will require several rotations. - * 1) Rotate the hub +90 degrees around the X axis to get it standing upright with the logo pointing backwards on the robot - * 2) Next, rotate the hub +90 around the Y axis to get it facing to the right. - * 3) Finally rotate the hub +120 degrees around the Z axis to take the USB ports from vertical to sloping down 30 degrees and - * facing towards the back of the robot. - * - * So the X,Y,Z rotations would be 90,90,120 - */ - - // The next three lines define the desired axis rotations. - // To Do: EDIT these values to match YOUR mounting configuration. - double xRotation = 0; // enter the desired X rotation angle here. - double yRotation = 0; // enter the desired Y rotation angle here. - double zRotation = 0; // enter the desired Z rotation angle here. - - Orientation hubRotation = xyzOrientation(xRotation, yRotation, zRotation); - - // Now initialize the IMU with this mounting orientation - RevHubOrientationOnRobot orientationOnRobot = new RevHubOrientationOnRobot(hubRotation); - imu.initialize(new IMU.Parameters(orientationOnRobot)); - - // Loop and update the dashboard - while (!isStopRequested()) { - telemetry.addData("Hub orientation", "X=%.1f, Y=%.1f, Z=%.1f \n", xRotation, yRotation, zRotation); - - // Check to see if heading reset is requested - if (gamepad1.y) { - telemetry.addData("Yaw", "Resetting\n"); - imu.resetYaw(); - } else { - telemetry.addData("Yaw", "Press Y (triangle) on Gamepad to reset\n"); - } - - // Retrieve Rotational Angles and Velocities - YawPitchRollAngles orientation = imu.getRobotYawPitchRollAngles(); - AngularVelocity angularVelocity = imu.getRobotAngularVelocity(AngleUnit.DEGREES); - - telemetry.addData("Yaw (Z)", "%.2f Deg. (Heading)", orientation.getYaw(AngleUnit.DEGREES)); - telemetry.addData("Pitch (X)", "%.2f Deg.", orientation.getPitch(AngleUnit.DEGREES)); - telemetry.addData("Roll (Y)", "%.2f Deg.\n", orientation.getRoll(AngleUnit.DEGREES)); - telemetry.addData("Yaw (Z) velocity", "%.2f Deg/Sec", angularVelocity.zRotationRate); - telemetry.addData("Pitch (X) velocity", "%.2f Deg/Sec", angularVelocity.xRotationRate); - telemetry.addData("Roll (Y) velocity", "%.2f Deg/Sec", angularVelocity.yRotationRate); - telemetry.update(); - } - } -} diff --git a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorIMUOrthogonal.java b/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorIMUOrthogonal.java deleted file mode 100644 index af4c202..0000000 --- a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorIMUOrthogonal.java +++ /dev/null @@ -1,146 +0,0 @@ -/* Copyright (c) 2022 FIRST. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted (subject to the limitations in the disclaimer below) provided that - * the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list - * of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this - * list of conditions and the following disclaimer in the documentation and/or - * other materials provided with the distribution. - * - * Neither the name of FIRST nor the names of its contributors may be used to endorse or - * promote products derived from this software without specific prior written permission. - * - * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS - * LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.firstinspires.ftc.robotcontroller.external.samples; - -import com.qualcomm.hardware.rev.RevHubOrientationOnRobot; -import com.qualcomm.robotcore.eventloop.opmode.Disabled; -import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode; -import com.qualcomm.robotcore.eventloop.opmode.TeleOp; -import com.qualcomm.robotcore.hardware.IMU; -import org.firstinspires.ftc.robotcore.external.navigation.AngleUnit; -import org.firstinspires.ftc.robotcore.external.navigation.AngularVelocity; -import org.firstinspires.ftc.robotcore.external.navigation.YawPitchRollAngles; - -/* - * This OpMode shows how to use the new universal IMU interface. This - * interface may be used with the BNO055 IMU or the BHI260 IMU. It assumes that an IMU is configured - * on the robot with the name "imu". - * - * The sample will display the current Yaw, Pitch and Roll of the robot.
- * With the correct orientation parameters selected, pitch/roll/yaw should act as follows: - * Pitch value should INCREASE as the robot is tipped UP at the front. (Rotation about X)
- * Roll value should INCREASE as the robot is tipped UP at the left side. (Rotation about Y)
- * Yaw value should INCREASE as the robot is rotated Counter Clockwise. (Rotation about Z)
- * - * The yaw can be reset (to zero) by pressing the Y button on the gamepad (Triangle on a PS4 controller) - * - * This specific sample assumes that the Hub is mounted on one of the three orthogonal planes - * (X/Y, X/Z or Y/Z) and that the Hub has only been rotated in a range of 90 degree increments. - * - * Note: if your Hub is mounted on a surface angled at some non-90 Degree multiple (like 30) look at - * the alternative SensorIMUNonOrthogonal sample in this folder. - * - * This "Orthogonal" requirement means that: - * - * 1) The Logo printed on the top of the Hub can ONLY be pointing in one of six directions: - * FORWARD, BACKWARD, UP, DOWN, LEFT and RIGHT. - * - * 2) The USB ports can only be pointing in one of the same six directions:
- * FORWARD, BACKWARD, UP, DOWN, LEFT and RIGHT. - * - * So, To fully define how your Hub is mounted to the robot, you must simply specify:
- * logoFacingDirection
- * usbFacingDirection - * - * Use Android Studio to Copy this Class, and Paste it into your team's code folder with a new name. - * Remove or comment out the @Disabled line to add this OpMode to the Driver Station OpMode list. - * - * Finally, choose the two correct parameters to define how your Hub is mounted and edit this OpMode - * to use those parameters. - */ -@TeleOp(name = "Sensor: IMU Orthogonal", group = "Sensor") -@Disabled // Comment this out to add to the OpMode list -public class SensorIMUOrthogonal extends LinearOpMode -{ - // The IMU sensor object - IMU imu; - - //---------------------------------------------------------------------------------------------- - // Main logic - //---------------------------------------------------------------------------------------------- - - @Override public void runOpMode() throws InterruptedException { - - // Retrieve and initialize the IMU. - // This sample expects the IMU to be in a REV Hub and named "imu". - imu = hardwareMap.get(IMU.class, "imu"); - - /* Define how the hub is mounted on the robot to get the correct Yaw, Pitch and Roll values. - * - * Two input parameters are required to fully specify the Orientation. - * The first parameter specifies the direction the printed logo on the Hub is pointing. - * The second parameter specifies the direction the USB connector on the Hub is pointing. - * All directions are relative to the robot, and left/right is as-viewed from behind the robot. - * - * If you are using a REV 9-Axis IMU, you can use the Rev9AxisImuOrientationOnRobot class instead of the - * RevHubOrientationOnRobot class, which has an I2cPortFacingDirection instead of a UsbFacingDirection. - */ - - /* The next two lines define Hub orientation. - * The Default Orientation (shown) is when a hub is mounted horizontally with the printed logo pointing UP and the USB port pointing FORWARD. - * - * To Do: EDIT these two lines to match YOUR mounting configuration. - */ - RevHubOrientationOnRobot.LogoFacingDirection logoDirection = RevHubOrientationOnRobot.LogoFacingDirection.UP; - RevHubOrientationOnRobot.UsbFacingDirection usbDirection = RevHubOrientationOnRobot.UsbFacingDirection.FORWARD; - - RevHubOrientationOnRobot orientationOnRobot = new RevHubOrientationOnRobot(logoDirection, usbDirection); - - // Now initialize the IMU with this mounting orientation - // Note: if you choose two conflicting directions, this initialization will cause a code exception. - imu.initialize(new IMU.Parameters(orientationOnRobot)); - - // Loop and update the dashboard - while (!isStopRequested()) { - - telemetry.addData("Hub orientation", "Logo=%s USB=%s\n ", logoDirection, usbDirection); - - // Check to see if heading reset is requested - if (gamepad1.y) { - telemetry.addData("Yaw", "Resetting\n"); - imu.resetYaw(); - } else { - telemetry.addData("Yaw", "Press Y (triangle) on Gamepad to reset\n"); - } - - // Retrieve Rotational Angles and Velocities - YawPitchRollAngles orientation = imu.getRobotYawPitchRollAngles(); - AngularVelocity angularVelocity = imu.getRobotAngularVelocity(AngleUnit.DEGREES); - - telemetry.addData("Yaw (Z)", "%.2f Deg. (Heading)", orientation.getYaw(AngleUnit.DEGREES)); - telemetry.addData("Pitch (X)", "%.2f Deg.", orientation.getPitch(AngleUnit.DEGREES)); - telemetry.addData("Roll (Y)", "%.2f Deg.\n", orientation.getRoll(AngleUnit.DEGREES)); - telemetry.addData("Yaw (Z) velocity", "%.2f Deg/Sec", angularVelocity.zRotationRate); - telemetry.addData("Pitch (X) velocity", "%.2f Deg/Sec", angularVelocity.xRotationRate); - telemetry.addData("Roll (Y) velocity", "%.2f Deg/Sec", angularVelocity.yRotationRate); - telemetry.update(); - } - } -} diff --git a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorKLNavxMicro.java b/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorKLNavxMicro.java deleted file mode 100644 index ccc945f..0000000 --- a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorKLNavxMicro.java +++ /dev/null @@ -1,128 +0,0 @@ -/* Copyright (c) 2017 FIRST. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted (subject to the limitations in the disclaimer below) provided that - * the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list - * of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this - * list of conditions and the following disclaimer in the documentation and/or - * other materials provided with the distribution. - * - * Neither the name of FIRST nor the names of its contributors may be used to endorse or - * promote products derived from this software without specific prior written permission. - * - * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS - * LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.firstinspires.ftc.robotcontroller.external.samples; - -import com.qualcomm.hardware.kauailabs.NavxMicroNavigationSensor; -import com.qualcomm.robotcore.eventloop.opmode.Disabled; -import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode; -import com.qualcomm.robotcore.eventloop.opmode.TeleOp; -import com.qualcomm.robotcore.hardware.Gyroscope; -import com.qualcomm.robotcore.hardware.IntegratingGyroscope; -import com.qualcomm.robotcore.util.ElapsedTime; -import org.firstinspires.ftc.robotcore.external.navigation.AngleUnit; -import org.firstinspires.ftc.robotcore.external.navigation.AngularVelocity; -import org.firstinspires.ftc.robotcore.external.navigation.AxesOrder; -import org.firstinspires.ftc.robotcore.external.navigation.AxesReference; -import org.firstinspires.ftc.robotcore.external.navigation.Orientation; - -/* - * This OpMode shows how to use Kauai Labs navX Micro Robotics Navigation - * Sensor. It assumes that the sensor is configured with a name of "navx". - * - * Use Android Studio to Copy this Class, and Paste it into your team's code folder with a new name. - * Remove or comment out the @Disabled line to add this OpMode to the Driver Station OpMode list - */ -@TeleOp(name = "Sensor: KL navX Micro", group = "Sensor") -@Disabled -public class SensorKLNavxMicro extends LinearOpMode { - - /** In this sample, for illustration purposes we use two interfaces on the one gyro object. - * That's likely atypical: you'll probably use one or the other in any given situation, - * depending on what you're trying to do. {@link IntegratingGyroscope} (and it's base interface, - * {@link Gyroscope}) are common interfaces supported by possibly several different gyro - * implementations. {@link NavxMicroNavigationSensor}, by contrast, provides functionality that - * is unique to the navX Micro sensor. - */ - IntegratingGyroscope gyro; - NavxMicroNavigationSensor navxMicro; - - // A timer helps provide feedback while calibration is taking place - ElapsedTime timer = new ElapsedTime(); - - @Override public void runOpMode() throws InterruptedException { - // Get a reference to a Modern Robotics GyroSensor object. We use several interfaces - // on this object to illustrate which interfaces support which functionality. - navxMicro = hardwareMap.get(NavxMicroNavigationSensor.class, "navx"); - gyro = (IntegratingGyroscope)navxMicro; - // If you're only interested int the IntegratingGyroscope interface, the following will suffice. - // gyro = hardwareMap.get(IntegratingGyroscope.class, "navx"); - - // The gyro automatically starts calibrating. This takes a few seconds. - telemetry.log().add("Gyro Calibrating. Do Not Move!"); - - // Wait until the gyro calibration is complete - timer.reset(); - while (navxMicro.isCalibrating()) { - telemetry.addData("calibrating", "%s", Math.round(timer.seconds())%2==0 ? "|.." : "..|"); - telemetry.update(); - Thread.sleep(50); - } - telemetry.log().clear(); telemetry.log().add("Gyro Calibrated. Press Start."); - telemetry.clear(); telemetry.update(); - - // Wait for the start button to be pressed - waitForStart(); - telemetry.log().clear(); - - while (opModeIsActive()) { - - // Read dimensionalized data from the gyro. This gyro can report angular velocities - // about all three axes. Additionally, it internally integrates the Z axis to - // be able to report an absolute angular Z orientation. - AngularVelocity rates = gyro.getAngularVelocity(AngleUnit.DEGREES); - Orientation angles = gyro.getAngularOrientation(AxesReference.INTRINSIC, AxesOrder.ZYX, AngleUnit.DEGREES); - - telemetry.addLine() - .addData("dx", formatRate(rates.xRotationRate)) - .addData("dy", formatRate(rates.yRotationRate)) - .addData("dz", "%s deg/s", formatRate(rates.zRotationRate)); - - telemetry.addLine() - .addData("heading", formatAngle(angles.angleUnit, angles.firstAngle)) - .addData("roll", formatAngle(angles.angleUnit, angles.secondAngle)) - .addData("pitch", "%s deg", formatAngle(angles.angleUnit, angles.thirdAngle)); - telemetry.update(); - - idle(); // Always call idle() at the bottom of your while(opModeIsActive()) loop - } - } - - String formatRate(float rate) { - return String.format("%.3f", rate); - } - - String formatAngle(AngleUnit angleUnit, double angle) { - return formatDegrees(AngleUnit.DEGREES.fromUnit(angleUnit, angle)); - } - - String formatDegrees(double degrees){ - return String.format("%.1f", AngleUnit.DEGREES.normalize(degrees)); - } -} diff --git a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorLimelight3A.java b/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorLimelight3A.java deleted file mode 100644 index 6c1f702..0000000 --- a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorLimelight3A.java +++ /dev/null @@ -1,159 +0,0 @@ -/* -Copyright (c) 2024 Limelight Vision - -All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted (subject to the limitations in the disclaimer below) provided that -the following conditions are met: - -Redistributions of source code must retain the above copyright notice, this list -of conditions and the following disclaimer. - -Redistributions in binary form must reproduce the above copyright notice, this -list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. - -Neither the name of FIRST nor the names of its contributors may be used to -endorse or promote products derived from this software without specific prior -written permission. - -NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS -LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR -TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF -THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ -package org.firstinspires.ftc.robotcontroller.external.samples; - -import com.qualcomm.hardware.limelightvision.LLResult; -import com.qualcomm.hardware.limelightvision.LLResultTypes; -import com.qualcomm.hardware.limelightvision.LLStatus; -import com.qualcomm.hardware.limelightvision.Limelight3A; -import com.qualcomm.robotcore.eventloop.opmode.Disabled; -import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode; -import com.qualcomm.robotcore.eventloop.opmode.TeleOp; - -import org.firstinspires.ftc.robotcore.external.navigation.Pose3D; - -import java.util.List; - -/* - * This OpMode illustrates how to use the Limelight3A Vision Sensor. - * - * @see Limelight - * - * Notes on configuration: - * - * The device presents itself, when plugged into a USB port on a Control Hub as an ethernet - * interface. A DHCP server running on the Limelight automatically assigns the Control Hub an - * ip address for the new ethernet interface. - * - * Since the Limelight is plugged into a USB port, it will be listed on the top level configuration - * activity along with the Control Hub Portal and other USB devices such as webcams. Typically - * serial numbers are displayed below the device's names. In the case of the Limelight device, the - * Control Hub's assigned ip address for that ethernet interface is used as the "serial number". - * - * Tapping the Limelight's name, transitions to a new screen where the user can rename the Limelight - * and specify the Limelight's ip address. Users should take care not to confuse the ip address of - * the Limelight itself, which can be configured through the Limelight settings page via a web browser, - * and the ip address the Limelight device assigned the Control Hub and which is displayed in small text - * below the name of the Limelight on the top level configuration screen. - */ -@TeleOp(name = "Sensor: Limelight3A", group = "Sensor") -@Disabled -public class SensorLimelight3A extends LinearOpMode { - - private Limelight3A limelight; - - @Override - public void runOpMode() throws InterruptedException - { - limelight = hardwareMap.get(Limelight3A.class, "limelight"); - - telemetry.setMsTransmissionInterval(11); - - limelight.pipelineSwitch(0); - - /* - * Starts polling for data. If you neglect to call start(), getLatestResult() will return null. - */ - limelight.start(); - - telemetry.addData(">", "Robot Ready. Press Play."); - telemetry.update(); - waitForStart(); - - while (opModeIsActive()) { - LLStatus status = limelight.getStatus(); - telemetry.addData("Name", "%s", - status.getName()); - telemetry.addData("LL", "Temp: %.1fC, CPU: %.1f%%, FPS: %d", - status.getTemp(), status.getCpu(),(int)status.getFps()); - telemetry.addData("Pipeline", "Index: %d, Type: %s", - status.getPipelineIndex(), status.getPipelineType()); - - LLResult result = limelight.getLatestResult(); - if (result != null) { - // Access general information - Pose3D botpose = result.getBotpose(); - double captureLatency = result.getCaptureLatency(); - double targetingLatency = result.getTargetingLatency(); - double parseLatency = result.getParseLatency(); - telemetry.addData("LL Latency", captureLatency + targetingLatency); - telemetry.addData("Parse Latency", parseLatency); - telemetry.addData("PythonOutput", java.util.Arrays.toString(result.getPythonOutput())); - - if (result.isValid()) { - telemetry.addData("tx", result.getTx()); - telemetry.addData("txnc", result.getTxNC()); - telemetry.addData("ty", result.getTy()); - telemetry.addData("tync", result.getTyNC()); - - telemetry.addData("Botpose", botpose.toString()); - - // Access barcode results - List barcodeResults = result.getBarcodeResults(); - for (LLResultTypes.BarcodeResult br : barcodeResults) { - telemetry.addData("Barcode", "Data: %s", br.getData()); - } - - // Access classifier results - List classifierResults = result.getClassifierResults(); - for (LLResultTypes.ClassifierResult cr : classifierResults) { - telemetry.addData("Classifier", "Class: %s, Confidence: %.2f", cr.getClassName(), cr.getConfidence()); - } - - // Access detector results - List detectorResults = result.getDetectorResults(); - for (LLResultTypes.DetectorResult dr : detectorResults) { - telemetry.addData("Detector", "Class: %s, Area: %.2f", dr.getClassName(), dr.getTargetArea()); - } - - // Access fiducial results - List fiducialResults = result.getFiducialResults(); - for (LLResultTypes.FiducialResult fr : fiducialResults) { - telemetry.addData("Fiducial", "ID: %d, Family: %s, X: %.2f, Y: %.2f", fr.getFiducialId(), fr.getFamily(),fr.getTargetXDegrees(), fr.getTargetYDegrees()); - } - - // Access color results - List colorResults = result.getColorResults(); - for (LLResultTypes.ColorResult cr : colorResults) { - telemetry.addData("Color", "X: %.2f, Y: %.2f", cr.getTargetXDegrees(), cr.getTargetYDegrees()); - } - } - } else { - telemetry.addData("Limelight", "No data available"); - } - - telemetry.update(); - } - limelight.stop(); - } -} diff --git a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorMRColor.java b/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorMRColor.java deleted file mode 100644 index 32b37e0..0000000 --- a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorMRColor.java +++ /dev/null @@ -1,138 +0,0 @@ -/* Copyright (c) 2017 FIRST. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted (subject to the limitations in the disclaimer below) provided that - * the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list - * of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this - * list of conditions and the following disclaimer in the documentation and/or - * other materials provided with the distribution. - * - * Neither the name of FIRST nor the names of its contributors may be used to endorse or - * promote products derived from this software without specific prior written permission. - * - * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS - * LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.firstinspires.ftc.robotcontroller.external.samples; - -import android.app.Activity; -import android.graphics.Color; -import android.view.View; -import com.qualcomm.robotcore.eventloop.opmode.Disabled; -import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode; -import com.qualcomm.robotcore.eventloop.opmode.TeleOp; -import com.qualcomm.robotcore.hardware.ColorSensor; - -/* - * - * This OpMode that shows how to use - * a Modern Robotics Color Sensor. - * - * The OpMode assumes that the color sensor - * is configured with a name of "sensor_color". - * - * You can use the X button on gamepad1 to toggle the LED on and off. - * - * Use Android Studio to Copy this Class, and Paste it into your team's code folder with a new name. - * Remove or comment out the @Disabled line to add this OpMode to the Driver Station OpMode list - */ -@TeleOp(name = "Sensor: MR Color", group = "Sensor") -@Disabled -public class SensorMRColor extends LinearOpMode { - - ColorSensor colorSensor; // Hardware Device Object - - - @Override - public void runOpMode() { - - // hsvValues is an array that will hold the hue, saturation, and value information. - float hsvValues[] = {0F,0F,0F}; - - // values is a reference to the hsvValues array. - final float values[] = hsvValues; - - // get a reference to the RelativeLayout so we can change the background - // color of the Robot Controller app to match the hue detected by the RGB sensor. - int relativeLayoutId = hardwareMap.appContext.getResources().getIdentifier("RelativeLayout", "id", hardwareMap.appContext.getPackageName()); - final View relativeLayout = ((Activity) hardwareMap.appContext).findViewById(relativeLayoutId); - - // bPrevState and bCurrState represent the previous and current state of the button. - boolean bPrevState = false; - boolean bCurrState = false; - - // bLedOn represents the state of the LED. - boolean bLedOn = true; - - // get a reference to our ColorSensor object. - colorSensor = hardwareMap.get(ColorSensor.class, "sensor_color"); - - // Set the LED in the beginning - colorSensor.enableLed(bLedOn); - - // wait for the start button to be pressed. - waitForStart(); - - // while the OpMode is active, loop and read the RGB data. - // Note we use opModeIsActive() as our loop condition because it is an interruptible method. - while (opModeIsActive()) { - - // check the status of the x button on either gamepad. - bCurrState = gamepad1.x; - - // check for button state transitions. - if (bCurrState && (bCurrState != bPrevState)) { - - // button is transitioning to a pressed state. So Toggle LED - bLedOn = !bLedOn; - colorSensor.enableLed(bLedOn); - } - - // update previous state variable. - bPrevState = bCurrState; - - // convert the RGB values to HSV values. - Color.RGBToHSV(colorSensor.red() * 8, colorSensor.green() * 8, colorSensor.blue() * 8, hsvValues); - - // send the info back to driver station using telemetry function. - telemetry.addData("LED", bLedOn ? "On" : "Off"); - telemetry.addData("Clear", colorSensor.alpha()); - telemetry.addData("Red ", colorSensor.red()); - telemetry.addData("Green", colorSensor.green()); - telemetry.addData("Blue ", colorSensor.blue()); - telemetry.addData("Hue", hsvValues[0]); - - // change the background color to match the color detected by the RGB sensor. - // pass a reference to the hue, saturation, and value array as an argument - // to the HSVToColor method. - relativeLayout.post(new Runnable() { - public void run() { - relativeLayout.setBackgroundColor(Color.HSVToColor(0xff, values)); - } - }); - - telemetry.update(); - } - - // Set the panel back to the default color - relativeLayout.post(new Runnable() { - public void run() { - relativeLayout.setBackgroundColor(Color.WHITE); - } - }); - } -} diff --git a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorMRGyro.java b/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorMRGyro.java deleted file mode 100644 index 91c0062..0000000 --- a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorMRGyro.java +++ /dev/null @@ -1,160 +0,0 @@ -/* Copyright (c) 2017 FIRST. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted (subject to the limitations in the disclaimer below) provided that - * the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list - * of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this - * list of conditions and the following disclaimer in the documentation and/or - * other materials provided with the distribution. - * - * Neither the name of FIRST nor the names of its contributors may be used to endorse or - * promote products derived from this software without specific prior written permission. - * - * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS - * LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.firstinspires.ftc.robotcontroller.external.samples; - -import com.qualcomm.hardware.modernrobotics.ModernRoboticsI2cGyro; -import com.qualcomm.robotcore.eventloop.opmode.Disabled; -import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode; -import com.qualcomm.robotcore.eventloop.opmode.TeleOp; -import com.qualcomm.robotcore.hardware.IntegratingGyroscope; -import com.qualcomm.robotcore.util.ElapsedTime; -import org.firstinspires.ftc.robotcore.external.navigation.AngleUnit; -import org.firstinspires.ftc.robotcore.external.navigation.AngularVelocity; -import org.firstinspires.ftc.robotcore.external.navigation.AxesOrder; -import org.firstinspires.ftc.robotcore.external.navigation.AxesReference; - -/* - * This OpMode shows how to use the Modern Robotics Gyro. - * - * The OpMode assumes that the gyro sensor is attached to a Device Interface Module - * I2C channel and is configured with a name of "gyro". - * - * Use Android Studio to Copy this Class, and Paste it into your team's code folder with a new name. - * Remove or comment out the @Disabled line to add this OpMode to the Driver Station OpMode list -*/ -@TeleOp(name = "Sensor: MR Gyro", group = "Sensor") -@Disabled -public class SensorMRGyro extends LinearOpMode { - - /* In this sample, for illustration purposes we use two interfaces on the one gyro object. - * That's likely atypical: you'll probably use one or the other in any given situation, - * depending on what you're trying to do. {@link IntegratingGyroscope} (and it's base interface, - * {@link Gyroscope}) are common interfaces supported by possibly several different gyro - * implementations. {@link ModernRoboticsI2cGyro}, by contrast, provides functionality that - * is unique to the Modern Robotics gyro sensor. - */ - IntegratingGyroscope gyro; - ModernRoboticsI2cGyro modernRoboticsI2cGyro; - - // A timer helps provide feedback while calibration is taking place - ElapsedTime timer = new ElapsedTime(); - - @Override - public void runOpMode() { - - boolean lastResetState = false; - boolean curResetState = false; - - // Get a reference to a Modern Robotics gyro object. We use several interfaces - // on this object to illustrate which interfaces support which functionality. - modernRoboticsI2cGyro = hardwareMap.get(ModernRoboticsI2cGyro.class, "gyro"); - gyro = (IntegratingGyroscope)modernRoboticsI2cGyro; - // If you're only interested int the IntegratingGyroscope interface, the following will suffice. - // gyro = hardwareMap.get(IntegratingGyroscope.class, "gyro"); - // A similar approach will work for the Gyroscope interface, if that's all you need. - - // Start calibrating the gyro. This takes a few seconds and is worth performing - // during the initialization phase at the start of each OpMode. - telemetry.log().add("Gyro Calibrating. Do Not Move!"); - modernRoboticsI2cGyro.calibrate(); - - // Wait until the gyro calibration is complete - timer.reset(); - while (!isStopRequested() && modernRoboticsI2cGyro.isCalibrating()) { - telemetry.addData("calibrating", "%s", Math.round(timer.seconds())%2==0 ? "|.." : "..|"); - telemetry.update(); - sleep(50); - } - - telemetry.log().clear(); telemetry.log().add("Gyro Calibrated. Press Start."); - telemetry.clear(); telemetry.update(); - - // Wait for the start button to be pressed - waitForStart(); - telemetry.log().clear(); - telemetry.log().add("Press A & B to reset heading"); - - // Loop until we're asked to stop - while (opModeIsActive()) { - - // If the A and B buttons are pressed just now, reset Z heading. - curResetState = (gamepad1.a && gamepad1.b); - if (curResetState && !lastResetState) { - modernRoboticsI2cGyro.resetZAxisIntegrator(); - } - lastResetState = curResetState; - - // The raw() methods report the angular rate of change about each of the - // three axes directly as reported by the underlying sensor IC. - int rawX = modernRoboticsI2cGyro.rawX(); - int rawY = modernRoboticsI2cGyro.rawY(); - int rawZ = modernRoboticsI2cGyro.rawZ(); - int heading = modernRoboticsI2cGyro.getHeading(); - int integratedZ = modernRoboticsI2cGyro.getIntegratedZValue(); - - // Read dimensionalized data from the gyro. This gyro can report angular velocities - // about all three axes. Additionally, it internally integrates the Z axis to - // be able to report an absolute angular Z orientation. - AngularVelocity rates = gyro.getAngularVelocity(AngleUnit.DEGREES); - float zAngle = gyro.getAngularOrientation(AxesReference.INTRINSIC, AxesOrder.ZYX, AngleUnit.DEGREES).firstAngle; - - // Read administrative information from the gyro - int zAxisOffset = modernRoboticsI2cGyro.getZAxisOffset(); - int zAxisScalingCoefficient = modernRoboticsI2cGyro.getZAxisScalingCoefficient(); - - telemetry.addLine() - .addData("dx", formatRate(rates.xRotationRate)) - .addData("dy", formatRate(rates.yRotationRate)) - .addData("dz", "%s deg/s", formatRate(rates.zRotationRate)); - telemetry.addData("angle", "%s deg", formatFloat(zAngle)); - telemetry.addData("heading", "%3d deg", heading); - telemetry.addData("integrated Z", "%3d", integratedZ); - telemetry.addLine() - .addData("rawX", formatRaw(rawX)) - .addData("rawY", formatRaw(rawY)) - .addData("rawZ", formatRaw(rawZ)); - telemetry.addLine().addData("z offset", zAxisOffset).addData("z coeff", zAxisScalingCoefficient); - telemetry.update(); - } - } - - String formatRaw(int rawValue) { - return String.format("%d", rawValue); - } - - String formatRate(float rate) { - return String.format("%.3f", rate); - } - - String formatFloat(float rate) { - return String.format("%.3f", rate); - } - -} diff --git a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorMROpticalDistance.java b/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorMROpticalDistance.java deleted file mode 100644 index 6d2dfa3..0000000 --- a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorMROpticalDistance.java +++ /dev/null @@ -1,70 +0,0 @@ -/* Copyright (c) 2017 FIRST. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted (subject to the limitations in the disclaimer below) provided that - * the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list - * of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this - * list of conditions and the following disclaimer in the documentation and/or - * other materials provided with the distribution. - * - * Neither the name of FIRST nor the names of its contributors may be used to endorse or - * promote products derived from this software without specific prior written permission. - * - * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS - * LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.firstinspires.ftc.robotcontroller.external.samples; - -import com.qualcomm.robotcore.eventloop.opmode.Disabled; -import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode; -import com.qualcomm.robotcore.eventloop.opmode.TeleOp; -import com.qualcomm.robotcore.hardware.OpticalDistanceSensor; - -/* - * This OpMode shows how to use a Modern Robotics Optical Distance Sensor - * It assumes that the ODS sensor is configured with a name of "sensor_ods". - * - * Use Android Studio to Copy this Class, and Paste it into your team's code folder with a new name. - * Remove or comment out the @Disabled line to add this OpMode to the Driver Station OpMode list - */ -@TeleOp(name = "Sensor: MR ODS", group = "Sensor") -@Disabled -public class SensorMROpticalDistance extends LinearOpMode { - - OpticalDistanceSensor odsSensor; // Hardware Device Object - - @Override - public void runOpMode() { - - // get a reference to our Light Sensor object. - odsSensor = hardwareMap.get(OpticalDistanceSensor.class, "sensor_ods"); - - // wait for the start button to be pressed. - waitForStart(); - - // while the OpMode is active, loop and read the light levels. - // Note we use opModeIsActive() as our loop condition because it is an interruptible method. - while (opModeIsActive()) { - - // send the info back to driver station using telemetry function. - telemetry.addData("Raw", odsSensor.getRawLightDetected()); - telemetry.addData("Normal", odsSensor.getLightDetected()); - - telemetry.update(); - } - } -} diff --git a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorMRRangeSensor.java b/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorMRRangeSensor.java deleted file mode 100644 index 2039ef9..0000000 --- a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorMRRangeSensor.java +++ /dev/null @@ -1,70 +0,0 @@ -/* Copyright (c) 2017 FIRST. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted (subject to the limitations in the disclaimer below) provided that - * the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list - * of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this - * list of conditions and the following disclaimer in the documentation and/or - * other materials provided with the distribution. - * - * Neither the name of FIRST nor the names of its contributors may be used to endorse or - * promote products derived from this software without specific prior written permission. - * - * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS - * LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.firstinspires.ftc.robotcontroller.external.samples; - -import com.qualcomm.hardware.modernrobotics.ModernRoboticsI2cRangeSensor; -import com.qualcomm.robotcore.eventloop.opmode.Disabled; -import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode; -import com.qualcomm.robotcore.eventloop.opmode.TeleOp; -import org.firstinspires.ftc.robotcore.external.navigation.DistanceUnit; - -/* - * This OpMode illustrates how to use the Modern Robotics Range Sensor. - * - * The OpMode assumes that the range sensor is configured with a name of "sensor_range". - * - * Use Android Studio to Copy this Class, and Paste it into your team's code folder with a new name. - * Remove or comment out the @Disabled line to add this OpMode to the Driver Station OpMode list - * - * @see MR Range Sensor - */ -@TeleOp(name = "Sensor: MR range sensor", group = "Sensor") -@Disabled // comment out or remove this line to enable this OpMode -public class SensorMRRangeSensor extends LinearOpMode { - - ModernRoboticsI2cRangeSensor rangeSensor; - - @Override public void runOpMode() { - - // get a reference to our compass - rangeSensor = hardwareMap.get(ModernRoboticsI2cRangeSensor.class, "sensor_range"); - - // wait for the start button to be pressed - waitForStart(); - - while (opModeIsActive()) { - telemetry.addData("raw ultrasonic", rangeSensor.rawUltrasonic()); - telemetry.addData("raw optical", rangeSensor.rawOptical()); - telemetry.addData("cm optical", "%.2f cm", rangeSensor.cmOptical()); - telemetry.addData("cm", "%.2f cm", rangeSensor.getDistance(DistanceUnit.CM)); - telemetry.update(); - } - } -} diff --git a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorOctoQuad.java b/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorOctoQuad.java deleted file mode 100644 index 2300dd7..0000000 --- a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorOctoQuad.java +++ /dev/null @@ -1,141 +0,0 @@ -/* - * Copyright (c) 2024 DigitalChickenLabs - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.firstinspires.ftc.robotcontroller.external.samples; - -import com.qualcomm.hardware.digitalchickenlabs.OctoQuad; -import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode; -import com.qualcomm.robotcore.eventloop.opmode.TeleOp; -import com.qualcomm.robotcore.eventloop.opmode.Disabled; -import org.firstinspires.ftc.robotcore.external.Telemetry; - -/* - * This OpMode illustrates how to use the DigitalChickenLabs OctoQuad Quadrature Encoder & Pulse Width Interface Module - * - * The OctoQuad has 8 input channels that can used to read either Relative Quadrature Encoders or Pulse-Width Absolute Encoder inputs. - * Relative Quadrature encoders are found on most FTC motors, and some stand-alone position sensors like the REV Thru-Bore encoder. - * Pulse-Width encoders are less common. The REV Thru-Bore encoder can provide its absolute position via a variable pulse width, - * as can several sonar rangefinders such as the MaxBotix MB1000 series. - * - * This basic sample shows how an OctoQuad can be used to read the position three Odometry pods fitted - * with REV Thru-Bore encoders. For a more advanced example showing additional OctoQuad capabilities, see the SensorOctoQuadAdv sample. - * - * This OpMode assumes that the OctoQuad is attached to an I2C interface named "octoquad" in the robot configuration. - * - * The code assumes the first three OctoQuad inputs are connected as follows - * - Chan 0: for measuring forward motion on the left side of the robot. - * - Chan 1: for measuring forward motion on the right side of the robot. - * - Chan 2: for measuring Lateral (strafing) motion. - * - * The encoder values may be reset to zero by pressing the X (left most) button on Gamepad 1. - * - * This sample does not show how to interpret these readings, just how to obtain and display them. - * - * Use Android Studio to Copy this Class, and Paste it into your team's code folder with a new name. - * Remove or comment out the @Disabled line to add this OpMode to the Driver Station OpMode list - * - * See the sensor's product page: https://www.tindie.com/products/35114/ - */ -@TeleOp(name = "OctoQuad Basic", group="OctoQuad") -@Disabled -public class SensorOctoQuad extends LinearOpMode { - - // Identify which encoder OctoQuad inputs are connected to each odometry pod. - private final int ODO_LEFT = 0; // Facing forward direction on left side of robot (Axial motion) - private final int ODO_RIGHT = 1; // Facing forward direction on right side or robot (Axial motion) - private final int ODO_PERP = 2; // Facing perpendicular direction at the center of the robot (Lateral motion) - - // Declare the OctoQuad object and members to store encoder positions and velocities - private OctoQuad octoquad; - - private int posLeft; - private int posRight; - private int posPerp; - - /** - * This function is executed when this OpMode is selected from the Driver Station. - */ - @Override - public void runOpMode() { - - // Connect to OctoQuad by referring to its name in the Robot Configuration. - octoquad = hardwareMap.get(OctoQuad.class, "octoquad"); - - // Read the Firmware Revision number from the OctoQuad and display it as telemetry. - telemetry.addData("OctoQuad Firmware Version ", octoquad.getFirmwareVersion()); - - // Reverse the count-direction of any encoder that is not what you require. - // e.g. if you push the robot forward and the left encoder counts down, then reverse it so it counts up. - octoquad.setSingleEncoderDirection(ODO_LEFT, OctoQuad.EncoderDirection.REVERSE); - octoquad.setSingleEncoderDirection(ODO_RIGHT, OctoQuad.EncoderDirection.FORWARD); - octoquad.setSingleEncoderDirection(ODO_PERP, OctoQuad.EncoderDirection.FORWARD); - - // Any changes that are made should be saved in FLASH just in case there is a sensor power glitch. - octoquad.saveParametersToFlash(); - - telemetry.addLine("\nPress START to read encoder values"); - telemetry.update(); - - waitForStart(); - - // Configure the telemetry for optimal display of data. - telemetry.setDisplayFormat(Telemetry.DisplayFormat.MONOSPACE); - telemetry.setMsTransmissionInterval(50); - - // Set all the encoder inputs to zero. - octoquad.resetAllPositions(); - - // Loop while displaying the odometry pod positions. - while (opModeIsActive()) { - telemetry.addData(">", "Press X to Reset Encoders\n"); - - // Check for X button to reset encoders. - if (gamepad1.x) { - // Reset the position of all encoders to zero. - octoquad.resetAllPositions(); - } - - // Read all the encoder data. Load into local members. - readOdometryPods(); - - // Display the values. - telemetry.addData("Left ", "%8d counts", posLeft); - telemetry.addData("Right", "%8d counts", posRight); - telemetry.addData("Perp ", "%8d counts", posPerp); - telemetry.update(); - } - } - - private void readOdometryPods() { - // For best performance, we should only perform ONE transaction with the OctoQuad each cycle. - // Since this example only needs to read positions from a few channels, we could use either - // readPositionRange(idxFirst, idxLast) to get a select number of sequential channels - // or - // readAllEncoderData() to get all 8 encoder readings - // - // Since both calls take almost the same amount of time, and the actual channels may not end up - // being sequential, we will read all of the encoder positions, and then pick out the ones we need. - OctoQuad.EncoderDataBlock data = octoquad.readAllEncoderData(); - posLeft = data.positions[ODO_LEFT]; - posRight = data.positions[ODO_RIGHT]; - posPerp = data.positions[ODO_PERP]; - } -} diff --git a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorOctoQuadAdv.java b/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorOctoQuadAdv.java deleted file mode 100644 index 2e807ce..0000000 --- a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorOctoQuadAdv.java +++ /dev/null @@ -1,277 +0,0 @@ -/* - * Copyright (c) 2024 DigitalChickenLabs - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.firstinspires.ftc.robotcontroller.external.samples; - -import com.qualcomm.hardware.digitalchickenlabs.OctoQuad; -import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode; -import com.qualcomm.robotcore.eventloop.opmode.TeleOp; -import com.qualcomm.robotcore.util.ElapsedTime; -import com.qualcomm.robotcore.util.MovingStatistics; -import com.qualcomm.robotcore.eventloop.opmode.Disabled; - - -import org.firstinspires.ftc.robotcore.external.Telemetry; -import org.firstinspires.ftc.robotcore.external.navigation.AngleUnit; - -import java.util.ArrayList; -import java.util.List; - -/* - * This OpMode illustrates how to use advanced features of the DigitalChickenLabs OctoQuad Quadrature Encoder & Pulse Width Interface Module - * - * The OctoQuad has 8 input channels that can used to read either Quadrature Encoder signals (like with most FTC motors) - * or Pulse-Width style Absolute Encoder inputs (eg: REV Through Bore encoder pulse width output). - * - * This OpMode illustrates several of the more advanced features of an OctoQuad, including Pulse Width measurement and velocity measurement. - * For a more basic OpMode just showing how to read encoder inputs, see the SensorOctoQuad sample. - * - * This OpMode assumes that the OctoQuad is attached to an I2C interface named "octoquad" in the robot configuration. - * - * One system that requires a lot of encoder inputs is a Swerve Drive system. - * Such a drive requires two encoders per drive module: one for position & velocity of the Drive motor/wheel, and one for position/angle of the Steering motor. - * The Drive motor usually requires a quadrature encoder, and the Steering motor requires an Absolute encoder. - * This quantity and combination of encoder inputs is a challenge, but a single OctoQuad could be used to read them all. - * - * This sample will configure an OctoQuad for a swerve drive, as follows - * - Configure 4 Relative Quadrature Encoder inputs and 4 Absolute Pulse-Width Encoder inputs - * - Configure a velocity sample interval of 25 mSec - * - Configure a pulse width input range of 1-1024 uSec for a REV Through Bore Encoder's Absolute Pulse output. - * - * An OctoSwerveDrive class will be created to initialize the OctoQuad, and manage the 4 swerve modules. - * An OctoSwerveModule class will be created to configure and read a single swerve module. - * - * Wiring: - * The OctoQuad will be configured to accept Quadrature encoders on the first four channels and Absolute (pulse width) encoders on the last four channels. - * - * The standard 4-pin to 4-pin cable can be used to connect each Driver Motor encoder to the OctoQuad. (channels 0-3) - * - * A modified version of the REV 6-4 pin cable (shipped with the encoder) connects the steering encoder to the OctoQuad. (channels 4-7) - * To connect the Absolute position signal from a REV Thru-Bore encoder to the OctoQuad, the Provided 6-pin to 4-pin cable will need to be modified. - * At the 6-pin connector end, move the yellow wire from its initial pin-3 position to the ABS pin-5 position. This can be done easily - * by using a small flathead screwdriver to lift the small white tab holding the metal wire crimp in place and gently pulling the wire out. - * See the OctoSwerveDrive() constructor below for the correct wheel/channel assignment. - * - * Use Android Studio to Copy this Class, and Paste it into your team's code folder with a new name. - * Remove or comment out the @Disabled line to add this OpMode to the Driver Station OpMode list - * - * Note: If you prefer, you can move the two support classes from this file, and place them in their own files. - * But leaving them in place is simpler for this example. - * - * See the sensor's product page: https://www.tindie.com/products/35114/ - */ -@TeleOp(name="OctoQuad Advanced", group="OctoQuad") -@Disabled -public class SensorOctoQuadAdv extends LinearOpMode { - - @Override - public void runOpMode() throws InterruptedException { - // Connect to the OctoQuad by looking up its name in the hardwareMap. - OctoQuad octoquad = hardwareMap.get(OctoQuad.class, "octoquad"); - - // Create the interface for the Swerve Drive Encoders - OctoSwerveDrive octoSwerveDrive = new OctoSwerveDrive(octoquad); - - // Display the OctoQuad firmware revision - telemetry.addLine("OctoQuad Firmware v" + octoquad.getFirmwareVersion()); - telemetry.addLine("\nPress START to read encoder values"); - telemetry.update(); - - waitForStart(); - - // Configure the telemetry for optimal display of data. - telemetry.setDisplayFormat(Telemetry.DisplayFormat.MONOSPACE); - telemetry.setMsTransmissionInterval(50); - - // Run stats to determine cycle times. - MovingStatistics avgTime = new MovingStatistics(100); - ElapsedTime elapsedTime = new ElapsedTime(); - - while (opModeIsActive()) { - telemetry.addData(">", "Press X to Reset Encoders\n"); - - if(gamepad1.x) { - octoquad.resetAllPositions(); - } - - // read all the swerve drive encoders and update positions and velocities. - octoSwerveDrive.updateModules(); - octoSwerveDrive.show(telemetry); - - // Update cycle time stats - avgTime.add(elapsedTime.nanoseconds()); - elapsedTime.reset(); - - telemetry.addData("Loop time", "%.1f mS", avgTime.getMean()/1000000); - telemetry.update(); - } - } -} - -// ============================ Internal (Inner) Classes ============================= - -/*** - * OctoSwerveDrive class manages 4 Swerve Modules - * - Performs general OctoQuad initialization - * - Creates 4 module classes, one for each swerve module - * - Updates swerve drive status by reading all data from OctoQuad and Updating each module - * - Displays all swerve drive data as telemetry - */ -class OctoSwerveDrive { - - private final OctoQuad octoquad; - private final List allModules = new ArrayList<>(); - - // members to hold encoder data for each module. - public final OctoSwerveModule LeftFront; - public final OctoSwerveModule RightFront; - public final OctoSwerveModule LeftBack; - public final OctoSwerveModule RightBack; - - // Prepare an object to hold an entire OctoQuad encoder readable register bank (pos and vel) - private final OctoQuad.EncoderDataBlock encoderDataBlock = new OctoQuad.EncoderDataBlock(); - - public OctoSwerveDrive(OctoQuad octoquad) { - this.octoquad = octoquad; - - // Clear out all prior settings and encoder data before setting up desired configuration - octoquad.resetEverything(); - - // Assume first 4 channels are relative encoders and the next 4 are absolute encoders - octoquad.setChannelBankConfig(OctoQuad.ChannelBankConfig.BANK1_QUADRATURE_BANK2_PULSE_WIDTH); - - // Create the four OctoSwerveModules, and add them to a 'list' for easier reference. - - // Note: The wheel/channel order is set here. Ensure your encoder cables match. - // - // Note: The angleOffset must be set for each module so a forward facing wheel shows a steer angle of 0 degrees. - // The process for determining the correct angleOffsets is as follows: - // 1) Set all the angleValues (below) to zero then build and deploy the code. - // 2) Physically rotate all the swerve drives so the wheels are facing forward in the desired 0 degree position - // 3) Run the OpMode, view the telemetry and record the "Degrees" value for each module. - // 4) Update the code by entering the recorded Degrees value for each module as the angleOffset (last) parameter in the lines below. - // - // Rebuild and deploy the new code. Verify that the telemetry now indicates 0 degrees when the wheels are facing forward. - // Also verify that the correct module values change appropriately when you manually spin (drive) and rotate (steer) a wheel. - - allModules.add(LeftFront = new OctoSwerveModule(octoquad, "LF ",0,0));// Drive = 0, Steer = 4 - allModules.add(RightFront = new OctoSwerveModule(octoquad, "RF ",1,0));// Drive = 1, Steer = 5 - allModules.add(LeftBack = new OctoSwerveModule(octoquad, "LB ",2,0));// Drive = 2, Steer = 6 - allModules.add(RightBack = new OctoSwerveModule(octoquad, "RB ",3,0));// Drive = 3, Steer = 7 - - // now make sure the settings persist through any power glitches. - octoquad.saveParametersToFlash(); - } - - /** - * Updates all 4 swerve modules - */ - public void updateModules() { - // Read full OctoQuad data block and then pass DataBlock to each swerve module to update themselves. - octoquad.readAllEncoderData(encoderDataBlock); - for (OctoSwerveModule module : allModules) { - module.updateModule(encoderDataBlock); - } - } - - /** - * Generate telemetry data for all modules. - * @param telemetry OpMode Telemetry object - */ - public void show(Telemetry telemetry) { - // create general header block and then have each module add its own telemetry - telemetry.addData("pos", " Count CPS Degree DPS"); - for (OctoSwerveModule module : allModules) { - module.show(telemetry); - } - } -} - -/*** - * The OctoSwerveModule class manages a single swerve module - */ -class OctoSwerveModule { - - public double driveCounts; - public double driveCountsPerSec; - public double steerDegrees; - public double steerDegreesPerSec; - - private final String name; - private final int channel; - private final double angleOffset; - private final double steerDirMult; - - private static final int VELOCITY_SAMPLE_INTERVAL_MS = 25; // To provide 40 updates per second. - private static final double DEGREES_PER_US = (360.0 / 1024.0); // based on REV Through Bore Encoder - private static final double VELOCITY_SAMPLES_PER_S = (1000.0 / VELOCITY_SAMPLE_INTERVAL_MS); - - // The correct drive and turn directions must be set for the Swerve Module based on the specific hardware geometry. - // Forward motion must generate an increasing drive count. - // Counter Clockwise steer rotation must generate an increasing Steer Angle (degrees) - private static final boolean INVERT_DRIVE_ENCODER = false; // Set true if forward motion decreases drive "Count" - private static final boolean INVERT_STEER_ENCODER = false; // Set true if counter clockwise steer action decreases steer "Degree" - - /*** - * @param octoquad provide access to configure OctoQuad - * @param name name used for telemetry display - * @param quadChannel Quadrature encoder channel. Pulse Width channel is this + 4 - * @param angleOffset Angle to subtract from absolute encoder to calibrate zero position. (see comments above) - */ - public OctoSwerveModule (OctoQuad octoquad, String name, int quadChannel, double angleOffset) { - this.name = name; - this.channel = quadChannel; - this.angleOffset = angleOffset; - this.steerDirMult = INVERT_STEER_ENCODER ? -1 : 1 ; // create a multiplier to flip the steer angle. - - // Set the drive encoder direction. Note the absolute encoder does not have built-in direction inversion. - octoquad.setSingleEncoderDirection(channel, INVERT_DRIVE_ENCODER ? OctoQuad.EncoderDirection.REVERSE : OctoQuad.EncoderDirection.FORWARD); - - // Set the velocity sample interval on both encoders - octoquad.setSingleVelocitySampleInterval(channel, VELOCITY_SAMPLE_INTERVAL_MS); - octoquad.setSingleVelocitySampleInterval(channel + 4, VELOCITY_SAMPLE_INTERVAL_MS); - - // Setup Absolute encoder pulse range to match REV Through Bore encoder. - octoquad.setSingleChannelPulseWidthParams (channel + 4, new OctoQuad.ChannelPulseWidthParams(1,1024)); - } - - /*** - * Calculate the Swerve module's position and velocity values - * @param encoderDataBlock most recent full data block read from OctoQuad. - */ - public void updateModule(OctoQuad.EncoderDataBlock encoderDataBlock) { - driveCounts = encoderDataBlock.positions[channel]; // get Counts. - driveCountsPerSec = encoderDataBlock.velocities[channel] * VELOCITY_SAMPLES_PER_S; // convert counts/interval to counts/sec - - // convert uS to degrees. Add in any possible direction flip. - steerDegrees = AngleUnit.normalizeDegrees((encoderDataBlock.positions[channel+ 4] * DEGREES_PER_US * steerDirMult) - angleOffset); - // convert uS/interval to deg per sec. Add in any possible direction flip. - steerDegreesPerSec = encoderDataBlock.velocities[channel + 4] * DEGREES_PER_US * steerDirMult * VELOCITY_SAMPLES_PER_S; - } - - /** - * Display the Swerve module's state as telemetry - * @param telemetry OpMode Telemetry object - */ - public void show(Telemetry telemetry) { - telemetry.addData(name, "%8.0f %7.0f %7.0f %6.0f", driveCounts, driveCountsPerSec, steerDegrees, steerDegreesPerSec); - } -} diff --git a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorREV2mDistance.java b/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorREV2mDistance.java deleted file mode 100644 index 13883c3..0000000 --- a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorREV2mDistance.java +++ /dev/null @@ -1,87 +0,0 @@ -/* -Copyright (c) 2018 FIRST - -All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted (subject to the limitations in the disclaimer below) provided that -the following conditions are met: - -Redistributions of source code must retain the above copyright notice, this list -of conditions and the following disclaimer. - -Redistributions in binary form must reproduce the above copyright notice, this -list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. - -Neither the name of FIRST nor the names of its contributors may be used to -endorse or promote products derived from this software without specific prior -written permission. - -NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS -LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR -TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF -THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ -package org.firstinspires.ftc.robotcontroller.external.samples; - -import com.qualcomm.hardware.rev.Rev2mDistanceSensor; -import com.qualcomm.robotcore.eventloop.opmode.Disabled; -import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode; -import com.qualcomm.robotcore.eventloop.opmode.TeleOp; -import com.qualcomm.robotcore.hardware.DistanceSensor; -import org.firstinspires.ftc.robotcore.external.navigation.DistanceUnit; - -/* - * This OpMode illustrates how to use the REV Robotics 2M Distance Sensor. - * - * The OpMode assumes that the sensor is configured with a name of "sensor_distance". - * - * Use Android Studio to Copy this Class, and Paste it into your team's code folder with a new name. - * Remove or comment out the @Disabled line to add this OpMode to the Driver Station OpMode list - * - * See the sensor's product page: https://www.revrobotics.com/rev-31-1505/ - */ -@TeleOp(name = "Sensor: REV2mDistance", group = "Sensor") -@Disabled -public class SensorREV2mDistance extends LinearOpMode { - - private DistanceSensor sensorDistance; - - @Override - public void runOpMode() { - // you can use this as a regular DistanceSensor. - sensorDistance = hardwareMap.get(DistanceSensor.class, "sensor_distance"); - - // you can also cast this to a Rev2mDistanceSensor if you want to use added - // methods associated with the Rev2mDistanceSensor class. - Rev2mDistanceSensor sensorTimeOfFlight = (Rev2mDistanceSensor) sensorDistance; - - telemetry.addData(">>", "Press start to continue"); - telemetry.update(); - - waitForStart(); - while(opModeIsActive()) { - // generic DistanceSensor methods. - telemetry.addData("deviceName", sensorDistance.getDeviceName() ); - telemetry.addData("range", String.format("%.01f mm", sensorDistance.getDistance(DistanceUnit.MM))); - telemetry.addData("range", String.format("%.01f cm", sensorDistance.getDistance(DistanceUnit.CM))); - telemetry.addData("range", String.format("%.01f m", sensorDistance.getDistance(DistanceUnit.METER))); - telemetry.addData("range", String.format("%.01f in", sensorDistance.getDistance(DistanceUnit.INCH))); - - // Rev2mDistanceSensor specific methods. - telemetry.addData("ID", String.format("%x", sensorTimeOfFlight.getModelID())); - telemetry.addData("did time out", Boolean.toString(sensorTimeOfFlight.didTimeoutOccur())); - - telemetry.update(); - } - } - -} diff --git a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorSparkFunOTOS.java b/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorSparkFunOTOS.java deleted file mode 100644 index 3a25230..0000000 --- a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorSparkFunOTOS.java +++ /dev/null @@ -1,156 +0,0 @@ -/* - SPDX-License-Identifier: MIT - - Copyright (c) 2024 SparkFun Electronics -*/ -package org.firstinspires.ftc.robotcontroller.external.samples; - -import com.qualcomm.robotcore.eventloop.opmode.Disabled; -import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode; -import com.qualcomm.robotcore.eventloop.opmode.TeleOp; - -import com.qualcomm.hardware.sparkfun.SparkFunOTOS; - -import org.firstinspires.ftc.robotcore.external.navigation.AngleUnit; -import org.firstinspires.ftc.robotcore.external.navigation.DistanceUnit; - -/* - * This OpMode illustrates how to use the SparkFun Qwiic Optical Tracking Odometry Sensor (OTOS) - * - * The OpMode assumes that the sensor is configured with a name of "sensor_otos". - * - * Use Android Studio to Copy this Class, and Paste it into your team's code folder with a new name. - * Remove or comment out the @Disabled line to add this OpMode to the Driver Station OpMode list - * - * See the sensor's product page: https://www.sparkfun.com/products/24904 - */ -@TeleOp(name = "Sensor: SparkFun OTOS", group = "Sensor") -@Disabled -public class SensorSparkFunOTOS extends LinearOpMode { - // Create an instance of the sensor - SparkFunOTOS myOtos; - - @Override - public void runOpMode() throws InterruptedException { - // Get a reference to the sensor - myOtos = hardwareMap.get(SparkFunOTOS.class, "sensor_otos"); - - // All the configuration for the OTOS is done in this helper method, check it out! - configureOtos(); - - // Wait for the start button to be pressed - waitForStart(); - - // Loop until the OpMode ends - while (opModeIsActive()) { - // Get the latest position, which includes the x and y coordinates, plus the - // heading angle - SparkFunOTOS.Pose2D pos = myOtos.getPosition(); - - // Reset the tracking if the user requests it - if (gamepad1.y) { - myOtos.resetTracking(); - } - - // Re-calibrate the IMU if the user requests it - if (gamepad1.x) { - myOtos.calibrateImu(); - } - - // Inform user of available controls - telemetry.addLine("Press Y (triangle) on Gamepad to reset tracking"); - telemetry.addLine("Press X (square) on Gamepad to calibrate the IMU"); - telemetry.addLine(); - - // Log the position to the telemetry - telemetry.addData("X coordinate", pos.x); - telemetry.addData("Y coordinate", pos.y); - telemetry.addData("Heading angle", pos.h); - - // Update the telemetry on the driver station - telemetry.update(); - } - } - - private void configureOtos() { - telemetry.addLine("Configuring OTOS..."); - telemetry.update(); - - // Set the desired units for linear and angular measurements. Can be either - // meters or inches for linear, and radians or degrees for angular. If not - // set, the default is inches and degrees. Note that this setting is not - // persisted in the sensor, so you need to set at the start of all your - // OpModes if using the non-default value. - // myOtos.setLinearUnit(DistanceUnit.METER); - myOtos.setLinearUnit(DistanceUnit.INCH); - // myOtos.setAngularUnit(AnguleUnit.RADIANS); - myOtos.setAngularUnit(AngleUnit.DEGREES); - - // Assuming you've mounted your sensor to a robot and it's not centered, - // you can specify the offset for the sensor relative to the center of the - // robot. The units default to inches and degrees, but if you want to use - // different units, specify them before setting the offset! Note that as of - // firmware version 1.0, these values will be lost after a power cycle, so - // you will need to set them each time you power up the sensor. For example, if - // the sensor is mounted 5 inches to the left (negative X) and 10 inches - // forward (positive Y) of the center of the robot, and mounted 90 degrees - // clockwise (negative rotation) from the robot's orientation, the offset - // would be {-5, 10, -90}. These can be any value, even the angle can be - // tweaked slightly to compensate for imperfect mounting (eg. 1.3 degrees). - SparkFunOTOS.Pose2D offset = new SparkFunOTOS.Pose2D(0, 0, 0); - myOtos.setOffset(offset); - - // Here we can set the linear and angular scalars, which can compensate for - // scaling issues with the sensor measurements. Note that as of firmware - // version 1.0, these values will be lost after a power cycle, so you will - // need to set them each time you power up the sensor. They can be any value - // from 0.872 to 1.127 in increments of 0.001 (0.1%). It is recommended to - // first set both scalars to 1.0, then calibrate the angular scalar, then - // the linear scalar. To calibrate the angular scalar, spin the robot by - // multiple rotations (eg. 10) to get a precise error, then set the scalar - // to the inverse of the error. Remember that the angle wraps from -180 to - // 180 degrees, so for example, if after 10 rotations counterclockwise - // (positive rotation), the sensor reports -15 degrees, the required scalar - // would be 3600/3585 = 1.004. To calibrate the linear scalar, move the - // robot a known distance and measure the error; do this multiple times at - // multiple speeds to get an average, then set the linear scalar to the - // inverse of the error. For example, if you move the robot 100 inches and - // the sensor reports 103 inches, set the linear scalar to 100/103 = 0.971 - myOtos.setLinearScalar(1.0); - myOtos.setAngularScalar(1.0); - - // The IMU on the OTOS includes a gyroscope and accelerometer, which could - // have an offset. Note that as of firmware version 1.0, the calibration - // will be lost after a power cycle; the OTOS performs a quick calibration - // when it powers up, but it is recommended to perform a more thorough - // calibration at the start of all your OpModes. Note that the sensor must - // be completely stationary and flat during calibration! When calling - // calibrateImu(), you can specify the number of samples to take and whether - // to wait until the calibration is complete. If no parameters are provided, - // it will take 255 samples and wait until done; each sample takes about - // 2.4ms, so about 612ms total - myOtos.calibrateImu(); - - // Reset the tracking algorithm - this resets the position to the origin, - // but can also be used to recover from some rare tracking errors - myOtos.resetTracking(); - - // After resetting the tracking, the OTOS will report that the robot is at - // the origin. If your robot does not start at the origin, or you have - // another source of location information (eg. vision odometry), you can set - // the OTOS location to match and it will continue to track from there. - SparkFunOTOS.Pose2D currentPosition = new SparkFunOTOS.Pose2D(0, 0, 0); - myOtos.setPosition(currentPosition); - - // Get the hardware and firmware version - SparkFunOTOS.Version hwVersion = new SparkFunOTOS.Version(); - SparkFunOTOS.Version fwVersion = new SparkFunOTOS.Version(); - myOtos.getVersionInfo(hwVersion, fwVersion); - - telemetry.addLine("OTOS configured! Press start to get position data!"); - telemetry.addLine(); - telemetry.addLine(String.format("OTOS Hardware Version: v%d.%d", hwVersion.major, hwVersion.minor)); - telemetry.addLine(String.format("OTOS Firmware Version: v%d.%d", fwVersion.major, fwVersion.minor)); - telemetry.update(); - } -} diff --git a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorTouch.java b/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorTouch.java deleted file mode 100644 index 3d79447..0000000 --- a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorTouch.java +++ /dev/null @@ -1,78 +0,0 @@ -/* Copyright (c) 2017 FIRST. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted (subject to the limitations in the disclaimer below) provided that - * the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list - * of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this - * list of conditions and the following disclaimer in the documentation and/or - * other materials provided with the distribution. - * - * Neither the name of FIRST nor the names of its contributors may be used to endorse or - * promote products derived from this software without specific prior written permission. - * - * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS - * LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.firstinspires.ftc.robotcontroller.external.samples; - -import com.qualcomm.robotcore.eventloop.opmode.Disabled; -import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode; -import com.qualcomm.robotcore.eventloop.opmode.TeleOp; -import com.qualcomm.robotcore.hardware.TouchSensor; - -/* - * This OpMode demonstrates how to use a REV Robotics Touch Sensor, REV Robotics Magnetic Limit Switch, or other device - * that implements the TouchSensor interface. Any touch sensor that connects its output to ground when pressed - * (known as "active low") can be configured as a "REV Touch Sensor". This includes REV's Magnetic Limit Switch. - * - * The OpMode assumes that the touch sensor is configured with a name of "sensor_touch". - * - * A REV Robotics Touch Sensor must be configured on digital port number 1, 3, 5, or 7. - * A Magnetic Limit Switch can be configured on any digital port. - * - * Use Android Studio to Copy this Class, and Paste it into your team's code folder with a new name. - * Remove or comment out the @Disabled line to add this OpMode to the Driver Station OpMode list. - */ -@TeleOp(name = "Sensor: REV touch sensor", group = "Sensor") -@Disabled -public class SensorTouch extends LinearOpMode { - TouchSensor touchSensor; // Touch sensor Object - - @Override - public void runOpMode() { - - // get a reference to our touchSensor object. - touchSensor = hardwareMap.get(TouchSensor.class, "sensor_touch"); - - // wait for the start button to be pressed. - waitForStart(); - - // while the OpMode is active, loop and read whether the sensor is being pressed. - // Note we use opModeIsActive() as our loop condition because it is an interruptible method. - while (opModeIsActive()) { - - // send the info back to driver station using telemetry function. - if (touchSensor.isPressed()) { - telemetry.addData("Touch Sensor", "Is Pressed"); - } else { - telemetry.addData("Touch Sensor", "Is Not Pressed"); - } - - telemetry.update(); - } - } -} diff --git a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/UtilityCameraFrameCapture.java b/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/UtilityCameraFrameCapture.java deleted file mode 100644 index 69420cc..0000000 --- a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/UtilityCameraFrameCapture.java +++ /dev/null @@ -1,127 +0,0 @@ -/* - * Copyright (c) 2023 FIRST - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted (subject to the limitations in the disclaimer below) provided that - * the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list - * of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this - * list of conditions and the following disclaimer in the documentation and/or - * other materials provided with the distribution. - * - * Neither the name of FIRST nor the names of its contributors may be used to - * endorse or promote products derived from this software without specific prior - * written permission. - * - * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS - * LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR - * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.firstinspires.ftc.robotcontroller.external.samples; - -import android.util.Size; -import com.qualcomm.robotcore.eventloop.opmode.Disabled; -import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode; -import com.qualcomm.robotcore.eventloop.opmode.TeleOp; -import org.firstinspires.ftc.robotcore.external.hardware.camera.BuiltinCameraDirection; -import org.firstinspires.ftc.robotcore.external.hardware.camera.WebcamName; -import org.firstinspires.ftc.vision.VisionPortal; - -import java.util.Locale; - -/* - * This OpMode helps calibrate a webcam or RC phone camera, useful for AprilTag pose estimation - * with the FTC VisionPortal. It captures a camera frame (image) and stores it on the Robot Controller - * (Control Hub or RC phone), with each press of the gamepad button X (or Square). - * Full calibration instructions are here: - * - * https://ftc-docs.firstinspires.org/camera-calibration - * - * In Android Studio, copy this class into your "teamcode" folder with a new name. - * Remove or comment out the @Disabled line to add this OpMode to the Driver Station OpMode list. - * - * In OnBot Java, use "Add File" to add this OpMode from the list of Samples. - */ - -@TeleOp(name = "Utility: Camera Frame Capture", group = "Utility") -@Disabled -public class UtilityCameraFrameCapture extends LinearOpMode -{ - /* - * EDIT THESE PARAMETERS AS NEEDED - */ - final boolean USING_WEBCAM = false; - final BuiltinCameraDirection INTERNAL_CAM_DIR = BuiltinCameraDirection.BACK; - final int RESOLUTION_WIDTH = 640; - final int RESOLUTION_HEIGHT = 480; - - // Internal state - boolean lastX; - int frameCount; - long capReqTime; - - @Override - public void runOpMode() - { - VisionPortal portal; - - if (USING_WEBCAM) - { - portal = new VisionPortal.Builder() - .setCamera(hardwareMap.get(WebcamName.class, "Webcam 1")) - .setCameraResolution(new Size(RESOLUTION_WIDTH, RESOLUTION_HEIGHT)) - .build(); - } - else - { - portal = new VisionPortal.Builder() - .setCamera(INTERNAL_CAM_DIR) - .setCameraResolution(new Size(RESOLUTION_WIDTH, RESOLUTION_HEIGHT)) - .build(); - } - - while (!isStopRequested()) - { - boolean x = gamepad1.x; - - if (x && !lastX) - { - portal.saveNextFrameRaw(String.format(Locale.US, "CameraFrameCapture-%06d", frameCount++)); - capReqTime = System.currentTimeMillis(); - } - - lastX = x; - - telemetry.addLine("######## Camera Capture Utility ########"); - telemetry.addLine(String.format(Locale.US, " > Resolution: %dx%d", RESOLUTION_WIDTH, RESOLUTION_HEIGHT)); - telemetry.addLine(" > Press X (or Square) to capture a frame"); - telemetry.addData(" > Camera Status", portal.getCameraState()); - - if (capReqTime != 0) - { - telemetry.addLine("\nCaptured Frame!"); - } - - if (capReqTime != 0 && System.currentTimeMillis() - capReqTime > 1000) - { - capReqTime = 0; - } - - telemetry.update(); - } - } -} diff --git a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/UtilityOctoQuadConfigMenu.java b/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/UtilityOctoQuadConfigMenu.java deleted file mode 100644 index a962919..0000000 --- a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/UtilityOctoQuadConfigMenu.java +++ /dev/null @@ -1,812 +0,0 @@ -/* - * Copyright (c) 2024 DigitalChickenLabs - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.firstinspires.ftc.robotcontroller.external.samples; - -import com.qualcomm.hardware.digitalchickenlabs.OctoQuad; -import com.qualcomm.robotcore.eventloop.opmode.Disabled; -import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode; -import com.qualcomm.robotcore.eventloop.opmode.TeleOp; -import com.qualcomm.robotcore.hardware.Gamepad; - -import org.firstinspires.ftc.robotcore.external.Telemetry; - -import java.util.ArrayList; -import java.util.Stack; - -/* - * This OpMode illustrates how to use the DigitalChickenLabs OctoQuad Quadrature Encoder & Pulse Width Interface Module. - * - * The OctoQuad has 8 input channels that can used to read either Relative Quadrature Encoders or Pulse-Width Absolute Encoder inputs. - * Relative Quadrature encoders are found on most FTC motors, and some stand-alone position sensors like the REV Thru-Bore encoder. - * Pulse-Width encoders are less common. The REV Thru-Bore encoder can provide its absolute position via a variable pulse width, - * as can several sonar rangefinders such as the MaxBotix MB1000 series. - * - * This OpMode assumes that the OctoQuad is attached to an I2C interface named "octoquad" in the robot configuration. - * - * Remove or comment out the @Disabled line to add this OpMode to the Driver Station OpMode list - * - * Select, Init and run the "OctoQuad Configuration Tool" OpMode - * Read the blue User-Interface tips at the top of the telemetry screen. - * Use the UI buttons to navigate the menu and make any desired changes to the OctoQuad configuration. - * Use the Program Settings To FLASH option to make any changes permanent. - * - * See the sensor's product page: https://www.tindie.com/products/digitalchickenlabs/octoquad-8ch-quadrature-pulse-width-decoder/ - */ -@TeleOp(name = "OctoQuad Configuration Tool", group="OctoQuad") -@Disabled -public class UtilityOctoQuadConfigMenu extends LinearOpMode -{ - TelemetryMenu.MenuElement rootMenu = new TelemetryMenu.MenuElement("OctoQuad Config Menu", true); - TelemetryMenu.MenuElement menuHwInfo = new TelemetryMenu.MenuElement("Hardware Information", false); - TelemetryMenu.EnumOption optionI2cResetMode; - TelemetryMenu.EnumOption optionChannelBankConfig; - - TelemetryMenu.MenuElement menuEncoderDirections = new TelemetryMenu.MenuElement("Set Encoder Directions", false); - TelemetryMenu.BooleanOption[] optionsEncoderDirections = new TelemetryMenu.BooleanOption[OctoQuad.NUM_ENCODERS]; - - TelemetryMenu.MenuElement menuVelocityIntervals = new TelemetryMenu.MenuElement("Velocity Measurement Intervals", false); - TelemetryMenu.IntegerOption[] optionsVelocityIntervals = new TelemetryMenu.IntegerOption[OctoQuad.NUM_ENCODERS]; - - TelemetryMenu.MenuElement menuAbsParams = new TelemetryMenu.MenuElement("Abs. Encoder Pulse Width Params", false); - TelemetryMenu.IntegerOption[] optionsAbsParamsMax = new TelemetryMenu.IntegerOption[OctoQuad.NUM_ENCODERS]; - TelemetryMenu.IntegerOption[] optionsAbsParamsMin = new TelemetryMenu.IntegerOption[OctoQuad.NUM_ENCODERS]; - - TelemetryMenu.OptionElement optionProgramToFlash; - TelemetryMenu.OptionElement optionSendToRAM; - - TelemetryMenu.StaticClickableOption optionExit; - - OctoQuad octoquad; - - boolean error = false; - - @Override - public void runOpMode() - { - octoquad = hardwareMap.getAll(OctoQuad.class).get(0); - - if(octoquad.getChipId() != OctoQuad.OCTOQUAD_CHIP_ID) - { - telemetry.addLine("Error: cannot communicate with OctoQuad. Check your wiring and configuration and try again"); - telemetry.update(); - - error = true; - } - else - { - if(octoquad.getFirmwareVersion().maj != OctoQuad.SUPPORTED_FW_VERSION_MAJ) - { - telemetry.addLine("Error: The OctoQuad is running a different major firmware version than this driver was built for. Cannot run configuration tool"); - telemetry.update(); - - error = true; - } - } - - if(error) - { - waitForStart(); - return; - } - - telemetry.addLine("Retrieving current configuration from OctoQuad"); - telemetry.update(); - - optionExit = new TelemetryMenu.StaticClickableOption("Exit configuration menu") - { - @Override - void onClick() // called on OpMode thread - { - requestOpModeStop(); - } - }; - - optionI2cResetMode = new TelemetryMenu.EnumOption("I2C Reset Mode", OctoQuad.I2cRecoveryMode.values(), octoquad.getI2cRecoveryMode()); - optionChannelBankConfig = new TelemetryMenu.EnumOption("Channel Bank Modes", OctoQuad.ChannelBankConfig.values(), octoquad.getChannelBankConfig()); - - menuHwInfo.addChild(new TelemetryMenu.StaticItem("Board Firmware: v" + octoquad.getFirmwareVersion())); - //menuHwInfo.addChild(new TelemetryMenu.StaticItem("Board unique ID: FIXME")); - - for(int i = 0; i < OctoQuad.NUM_ENCODERS; i++) - { - optionsEncoderDirections[i] = new TelemetryMenu.BooleanOption( - String.format("Encoder %d direction", i), - octoquad.getSingleEncoderDirection(i) == OctoQuad.EncoderDirection.REVERSE, - "-", - "+"); - } - menuEncoderDirections.addChildren(optionsEncoderDirections); - - for(int i = 0; i < OctoQuad.NUM_ENCODERS; i++) - { - optionsVelocityIntervals[i] = new TelemetryMenu.IntegerOption( - String.format("Chan %d velocity intvl", i), - OctoQuad.MIN_VELOCITY_MEASUREMENT_INTERVAL_MS, - OctoQuad.MAX_VELOCITY_MEASUREMENT_INTERVAL_MS, - octoquad.getSingleVelocitySampleInterval(i)); - } - menuVelocityIntervals.addChildren(optionsVelocityIntervals); - - for(int i = 0; i < OctoQuad.NUM_ENCODERS; i++) - { - OctoQuad.ChannelPulseWidthParams params = octoquad.getSingleChannelPulseWidthParams(i); - - optionsAbsParamsMax[i] = new TelemetryMenu.IntegerOption( - String.format("Chan %d max pulse length", i), - OctoQuad.MIN_PULSE_WIDTH_US, - OctoQuad.MAX_PULSE_WIDTH_US, - params.max_length_us); - - optionsAbsParamsMin[i] = new TelemetryMenu.IntegerOption( - String.format("Chan %d min pulse length", i), - OctoQuad.MIN_PULSE_WIDTH_US, - OctoQuad.MAX_PULSE_WIDTH_US, - params.min_length_us); - } - menuAbsParams.addChildren(optionsAbsParamsMin); - menuAbsParams.addChildren(optionsAbsParamsMax); - - optionProgramToFlash = new TelemetryMenu.OptionElement() - { - String name = "Program Settings to FLASH"; - long lastClickTime = 0; - - @Override - protected String getDisplayText() - { - if(lastClickTime == 0) - { - return name; - } - else - { - if(System.currentTimeMillis() - lastClickTime < 1000) - { - return name + " **OK**"; - } - else - { - lastClickTime = 0; - return name; - } - } - } - - @Override - void onClick() - { - sendSettingsToRam(); - octoquad.saveParametersToFlash(); - lastClickTime = System.currentTimeMillis(); - } - }; - - optionSendToRAM = new TelemetryMenu.OptionElement() - { - String name = "Send Settings to RAM"; - long lastClickTime = 0; - - @Override - protected String getDisplayText() - { - if(lastClickTime == 0) - { - return name; - } - else - { - if(System.currentTimeMillis() - lastClickTime < 1000) - { - return name + " **OK**"; - } - else - { - lastClickTime = 0; - return name; - } - } - } - - @Override - void onClick() - { - sendSettingsToRam(); - lastClickTime = System.currentTimeMillis(); - } - }; - - rootMenu.addChild(menuHwInfo); - rootMenu.addChild(optionI2cResetMode); - rootMenu.addChild(optionChannelBankConfig); - rootMenu.addChild(menuEncoderDirections); - rootMenu.addChild(menuVelocityIntervals); - rootMenu.addChild(menuAbsParams); - rootMenu.addChild(optionProgramToFlash); - rootMenu.addChild(optionSendToRAM); - rootMenu.addChild(optionExit); - - TelemetryMenu menu = new TelemetryMenu(telemetry, rootMenu); - - while (!isStopRequested()) - { - menu.loop(gamepad1); - telemetry.update(); - sleep(20); - } - } - - void sendSettingsToRam() - { - for(int i = 0; i < OctoQuad.NUM_ENCODERS; i++) - { - octoquad.setSingleEncoderDirection(i, optionsEncoderDirections[i].getValue() ? OctoQuad.EncoderDirection.REVERSE : OctoQuad.EncoderDirection.FORWARD); - octoquad.setSingleVelocitySampleInterval(i, optionsVelocityIntervals[i].getValue()); - - OctoQuad.ChannelPulseWidthParams params = new OctoQuad.ChannelPulseWidthParams(); - params.max_length_us = optionsAbsParamsMax[i].getValue(); - params.min_length_us = optionsAbsParamsMin[i].getValue(); - - octoquad.setSingleChannelPulseWidthParams(i, params); - } - - octoquad.setI2cRecoveryMode((OctoQuad.I2cRecoveryMode) optionI2cResetMode.getValue()); - octoquad.setChannelBankConfig((OctoQuad.ChannelBankConfig) optionChannelBankConfig.getValue()); - } - - /* - * Copyright (c) 2023 OpenFTC Team - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - - public static class TelemetryMenu - { - private final MenuElement root; - private MenuElement currentLevel; - - private boolean dpadUpPrev; - private boolean dpadDnPrev; - private boolean dpadRightPrev; - private boolean dpadLeftPrev; - private boolean xPrev; - private boolean lbPrev; - - private int selectedIdx = 0; - private Stack selectedIdxStack = new Stack<>(); - - private final Telemetry telemetry; - - /** - * TelemetryMenu constructor - * @param telemetry pass in 'telemetry' from your OpMode - * @param root the root menu element - */ - public TelemetryMenu(Telemetry telemetry, MenuElement root) - { - this.root = root; - this.currentLevel = root; - this.telemetry = telemetry; - - telemetry.setDisplayFormat(Telemetry.DisplayFormat.HTML); - telemetry.setMsTransmissionInterval(50); - } - - /** - * Call this from inside your loop to put the current menu state into - * telemetry, and process gamepad inputs for navigating the menu - * @param gamepad the gamepad you want to use to navigate the menu - */ - public void loop(Gamepad gamepad) - { - // Capture current state of the gamepad buttons we care about; - // We can only look once or we risk a race condition - boolean dpadUp = gamepad.dpad_up; - boolean dpadDn = gamepad.dpad_down; - boolean dpadRight = gamepad.dpad_right; - boolean dpadLeft = gamepad.dpad_left; - boolean x = gamepad.x; - boolean lb = gamepad.left_bumper; - - // Figure out who our children our at this level - // and figure out which item is currently highlighted - // with the selection pointer - ArrayList children = currentLevel.children(); - Element currentSelection = children.get(selectedIdx); - - // Left and right are inputs to the selected item (if it's an Option) - if (currentSelection instanceof OptionElement) - { - if (dpadRight && !dpadRightPrev) // rising edge - { - ((OptionElement) currentSelection).onRightInput(); - } - else if (dpadLeft && !dpadLeftPrev) // rising edge - { - ((OptionElement) currentSelection).onLeftInput(); - } - } - - // Up and down navigate the current selection pointer - if (dpadUp && !dpadUpPrev) // rising edge - { - selectedIdx--; // Move selection pointer up - } - else if (dpadDn && !dpadDnPrev) // rising edge - { - selectedIdx++; // Move selection pointer down - } - - // Make selected index sane (don't let it go out of bounds) :eyes: - if (selectedIdx >= children.size()) - { - selectedIdx = children.size()-1; - } - else if (selectedIdx < 0) - { - selectedIdx = 0; - } - - // Select: either enter submenu or input to option - else if (x && !xPrev) // rising edge - { - // Select up element - if (currentSelection instanceof SpecialUpElement) - { - // We can only go up if we're not at the root level - if (currentLevel != root) - { - // Restore selection pointer to where it was before - selectedIdx = selectedIdxStack.pop(); - - // Change to the parent level - currentLevel = currentLevel.parent(); - } - } - // Input to option - else if (currentSelection instanceof OptionElement) - { - ((OptionElement) currentSelection).onClick(); - } - // Enter submenu - else if (currentSelection instanceof MenuElement) - { - // Save our current selection pointer so we can restore it - // later if the user navigates back up a level - selectedIdxStack.push(selectedIdx); - - // We have no idea what's in the submenu :monkey: so best to - // just set the selection pointer to the first element - selectedIdx = 0; - - // Now the current level becomes the submenu that the selection - // pointer was on - currentLevel = (MenuElement) currentSelection; - } - } - - // Go up a level - else if (lb && !lbPrev) - { - // We can only go up if we're not at the root level - if (currentLevel != root) - { - // Restore selection pointer to where it was before - selectedIdx = selectedIdxStack.pop(); - - // Change to the parent level - currentLevel = currentLevel.parent(); - } - } - - // Save the current button states so that we can look for - // the rising edge the next time around the loop :) - dpadUpPrev = dpadUp; - dpadDnPrev = dpadDn; - dpadRightPrev = dpadRight; - dpadLeftPrev = dpadLeft; - xPrev = x; - lbPrev = lb; - - // Start building the text display. - // First, we add the static directions for gamepad operation - StringBuilder builder = new StringBuilder(); - builder.append(""); - builder.append("Navigate items.....dpad up/down\n") - .append("Select.............X or Square\n") - .append("Edit option........dpad left/right\n") - .append("Up one level.......left bumper\n"); - builder.append(""); - builder.append("\n"); - - // Now actually add the menu options. We start by adding the name of the current menu level. - builder.append(""); - builder.append("Current Menu: ").append(currentLevel.name).append("\n"); - - // Now we loop through all the child elements of this level and add them - for (int i = 0; i < children.size(); i++) - { - // If the selection pointer is at this index, put a green dot in the box :) - if (selectedIdx == i) - { - builder.append("[] "); - } - // Otherwise, just put an empty box - else - { - builder.append("[ ] "); - } - - // Figure out who the selection pointer is pointing at :eyes: - Element e = children.get(i); - - // If it's pointing at a submenu, indicate that it's a submenu to the user - // by prefixing "> " to the name. - if (e instanceof MenuElement) - { - builder.append("> "); - } - - // Finally, add the element's name - builder.append(e.getDisplayText()); - - // We musn't forget the newline - builder.append("\n"); - } - - // Don't forget to close the font tag either - builder.append(""); - - // Build the string!!!! :nerd: - String menu = builder.toString(); - - // Add it to telemetry - telemetry.addLine(menu); - } - - public static class MenuElement extends Element - { - private String name; - private ArrayList children = new ArrayList<>(); - - /** - * Create a new MenuElement; may either be the root menu, or a submenu (set isRoot accordingly) - * @param name the name for this menu - * @param isRoot whether this is a root menu, or a submenu - */ - public MenuElement(String name, boolean isRoot) - { - this.name = name; - - // If it's not the root menu, we add the up one level option as the first element - if (!isRoot) - { - children.add(new SpecialUpElement()); - } - } - - /** - * Add a child element to this menu (may either be an Option or another menu) - * @param child the child element to add - */ - public void addChild(Element child) - { - child.setParent(this); - children.add(child); - } - - /** - * Add multiple child elements to this menu (may either be option, or another menu) - * @param children the children to add - */ - public void addChildren(Element[] children) - { - for (Element e : children) - { - e.setParent(this); - this.children.add(e); - } - } - - @Override - protected String getDisplayText() - { - return name; - } - - private ArrayList children() - { - return children; - } - } - - public static abstract class OptionElement extends Element - { - /** - * Override this to get notified when the element is clicked - */ - void onClick() {} - - /** - * Override this to get notified when the element gets a "left edit" input - */ - protected void onLeftInput() {} - - /** - * Override this to get notified when the element gets a "right edit" input - */ - protected void onRightInput() {} - } - - public static class EnumOption extends OptionElement - { - protected int idx = 0; - protected Enum[] e; - protected String name; - - public EnumOption(String name, Enum[] e) - { - this.e = e; - this.name = name; - } - - public EnumOption(String name, Enum[] e, Enum def) - { - this(name, e); - idx = def.ordinal(); - } - - @Override - public void onLeftInput() - { - idx++; - - if(idx > e.length-1) - { - idx = 0; - } - } - - @Override - public void onRightInput() - { - idx--; - - if(idx < 0) - { - idx = e.length-1; - } - } - - @Override - public void onClick() - { - //onRightInput(); - } - - @Override - protected String getDisplayText() - { - return String.format("%s: %s", name, e[idx].name()); - } - - public Enum getValue() - { - return e[idx]; - } - } - - public static class IntegerOption extends OptionElement - { - protected int i; - protected int min; - protected int max; - protected String name; - - public IntegerOption(String name, int min, int max, int def) - { - this.name = name; - this.min = min; - this.max = max; - this.i = def; - } - - @Override - public void onLeftInput() - { - i--; - - if(i < min) - { - i = max; - } - } - - @Override - public void onRightInput() - { - i++; - - if(i > max) - { - i = min; - } - } - - @Override - public void onClick() - { - //onRightInput(); - } - - @Override - protected String getDisplayText() - { - return String.format("%s: %d", name, i); - } - - public int getValue() - { - return i; - } - } - - static class BooleanOption extends OptionElement - { - private String name; - private boolean val = true; - - private String customTrue; - private String customFalse; - - BooleanOption(String name, boolean def) - { - this.name = name; - this.val = def; - } - - BooleanOption(String name, boolean def, String customTrue, String customFalse) - { - this(name, def); - this.customTrue = customTrue; - this.customFalse = customFalse; - } - - @Override - public void onLeftInput() - { - val = !val; - } - - @Override - public void onRightInput() - { - val = !val; - } - - @Override - public void onClick() - { - val = !val; - } - - @Override - protected String getDisplayText() - { - String valStr; - - if(customTrue != null && customFalse != null) - { - valStr = val ? customTrue : customFalse; - } - else - { - valStr = val ? "true" : "false"; - } - - return String.format("%s: %s", name, valStr); - } - - public boolean getValue() - { - return val; - } - } - - /** - * - */ - public static class StaticItem extends OptionElement - { - private String name; - - public StaticItem(String name) - { - this.name = name; - } - - @Override - protected String getDisplayText() - { - return name; - } - } - - public static abstract class StaticClickableOption extends OptionElement - { - private String name; - - public StaticClickableOption(String name) - { - this.name = name; - } - - abstract void onClick(); - - @Override - protected String getDisplayText() - { - return name; - } - } - - private static abstract class Element - { - private MenuElement parent; - - protected void setParent(MenuElement parent) - { - this.parent = parent; - } - - protected MenuElement parent() - { - return parent; - } - - protected abstract String getDisplayText(); - } - - private static class SpecialUpElement extends Element - { - @Override - protected String getDisplayText() - { - return ".. ↰ Up One Level"; - } - } - } -} diff --git a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/readme.md b/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/readme.md deleted file mode 100644 index 326978d..0000000 --- a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/readme.md +++ /dev/null @@ -1,45 +0,0 @@ - -## Caution -No Team-specific code should be placed or modified in this ``.../samples`` folder. - -Samples should be Copied from here, and then Pasted into the team's -[/TeamCode/src/main/java/org/firstinspires/ftc/teamcode](../../../../../../../../../../TeamCode/src/main/java/org/firstinspires/ftc/teamcode) - folder, using the Android Studio cut and paste commands. This automatically changes all file and -class names to be consistent. From there, the sample can be modified to suit the team's needs. - -For more detailed instructions see the /teamcode readme. - -### Naming of Samples - -To gain a better understanding of how the samples are organized, and how to interpret the -naming system, it will help to understand the conventions that were used during their creation. - -These conventions are described (in detail) in the sample_conventions.md file in this folder. - -To summarize: A range of different samples classes will reside in the java/external/samples. -The class names will follow a naming convention which indicates the purpose of each class. -The prefix of the name will be one of the following: - -Basic: This is a minimally functional OpMode used to illustrate the skeleton/structure - of a particular style of OpMode. These are bare bones examples. - -Sensor: This is a Sample OpMode that shows how to use a specific sensor. - It is not intended to drive a functioning robot, it is simply showing the minimal code - required to read and display the sensor values. - -Robot: This is a Sample OpMode that assumes a simple two-motor (differential) drive base. - It may be used to provide a common baseline driving OpMode, or - to demonstrate how a particular sensor or concept can be used to navigate. - -Concept: This is a sample OpMode that illustrates performing a specific function or concept. - These may be complex, but their operation should be explained clearly in the comments, - or the comments should reference an external doc, guide or tutorial. - Each OpMode should try to only demonstrate a single concept so they are easy to - locate based on their name. These OpModes may not produce a drivable robot. - -After the prefix, other conventions will apply: - -* Sensor class names are constructed as: Sensor - Company - Type -* Robot class names are constructed as: Robot - Mode - Action - OpModetype -* Concept class names are constructed as: Concept - Topic - OpModetype - diff --git a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/sample_conventions.md b/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/sample_conventions.md deleted file mode 100644 index e85e625..0000000 --- a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/sample_conventions.md +++ /dev/null @@ -1,113 +0,0 @@ -## Sample Class/Opmode conventions -#### V 1.1.0 8/9/2017 - -This document defines the FTC Sample OpMode and Class conventions. - -### OpMode Name - -To gain a better understanding of how the samples are organized, and how to interpret the -naming system, it will help to understand the conventions that were used during their creation. - -To summarize: A range of different samples classes will reside in the java/external/samples. -The class names will follow a naming convention which indicates the purpose of each class. -The prefix of the name will be one of the following: - -Basic: This is a minimally functional OpMode used to illustrate the skeleton/structure - of a particular style of OpMode. These are bare bones examples. - -Sensor: This is a Sample OpMode that shows how to use a specific sensor. - It is not intended to drive a functioning robot, it is simply showing the minimal code - required to read and display the sensor values. - -Robot: This is a Sample OpMode that assumes a simple two-motor (differential) drive base. - It may be used to provide a common baseline driving OpMode, or - to demonstrate how a particular sensor or concept can be used to navigate. - -Concept: This is a sample OpMode that illustrates performing a specific function or concept. - These may be complex, but their operation should be explained clearly in the comments, - or the comments should reference an external doc, guide or tutorial. - Each OpMode should try to only demonstrate a single concept so they are easy to - locate based on their name. These OpModes may not produce a drivable robot. - -Utility: This sample type is provided as a useful tool, or aide, to perform some specific development task. - It is not expected to be something you would include in your own robot code. - To use the tool, comment out the @Disabled annotation and build the App. - Read the comments found in the sample for an explanation of its intended use. - -After the prefix, other conventions will apply: - -* Sensor class names should constructed as: Sensor - Company - Type -* Robot class names should be constructed as: Robot - Mode - Action - OpModetype -* Concept class names should be constructed as: Concept - Topic - OpModetype - -### Sample OpMode Content/Style - -Code is formatted as per the Google Style Guide: - -https://google.github.io/styleguide/javaguide.html - -With “Sensor” and “Hardware” samples, the code should demonstrate the essential function, -and not be embellished with too much additional “clever” code. If a sensor has special -addressing needs, or has a variety of modes or outputs, these should be demonstrated as -simply as possible. - -Special programming methods, or robot control techniques should be reserved for “Concept” Samples, -and where possible, Samples should strive to only demonstrate a single concept, -eg: State machine coding, or a User Menu system, and not combine them into a single “all inclusive” -sample. This will prevent an “all inclusive” Sample being deleted just because one part of it -becomes obsolete. - -### Device Configuration Names - -The following device names are used in the external samples - -** Motors: -left_drive -right_drive -left_arm - -** Servos: -left_hand -right_hand -arm -claw - -** Sensors: -sensor_color -sensor_ir -sensor_light -sensor_ods -sensor_range -sensor_touch -sensor_color_distance -sensor_digital -digin -digout - -** Localization: -compass -gyro -imu -navx - -### Device Object Names - -Device Object names should use the same words as the device’s configuration name, but they -should be re-structured to be a suitable Java variable name. This should keep the same word order, -but adopt the style of beginning with a lower case letter, and then each subsequent word -starting with an upper case letter. - -Eg: from the examples above: tool, leftMotor, rightClawServo, rearLightSensor. - -Note: Sometimes it’s helpful to put the device type first, followed by the variant. -eg: motorLeft and motorRight, but this should only be done if the same word order -is used on the device configuration name. - -### OpMode code Comments - -Sample comments should read like normal code comments, that is, as an explanation of what the -sample code is doing. They should NOT be directives to the user, -like: “insert your joystick code here” as these comments typically aren’t -detailed enough to be useful. They also often get left in the code and become garbage. - -Instead, an example of the joystick code should be shown with a comment describing what it is doing. diff --git a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/internal/FtcOpModeRegister.java b/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/internal/FtcOpModeRegister.java deleted file mode 100644 index ceab67d..0000000 --- a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/internal/FtcOpModeRegister.java +++ /dev/null @@ -1,68 +0,0 @@ -/* Copyright (c) 2014, 2015 Qualcomm Technologies Inc - -All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted (subject to the limitations in the disclaimer below) provided that -the following conditions are met: - -Redistributions of source code must retain the above copyright notice, this list -of conditions and the following disclaimer. - -Redistributions in binary form must reproduce the above copyright notice, this -list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. - -Neither the name of Qualcomm Technologies Inc nor the names of its contributors -may be used to endorse or promote products derived from this software without -specific prior written permission. - -NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS -LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ - -package org.firstinspires.ftc.robotcontroller.internal; - -import com.qualcomm.robotcore.eventloop.opmode.OpModeManager; -import com.qualcomm.robotcore.eventloop.opmode.OpModeRegister; - -/** - * {@link FtcOpModeRegister} is responsible for registering OpModes for use in an FTC game. - * @see #register(OpModeManager) - */ -public class FtcOpModeRegister implements OpModeRegister { - - /** - * {@link #register(OpModeManager)} is called by the SDK game in order to register - * OpMode classes or instances that will participate in an FTC game. - * - * There are two mechanisms by which an OpMode may be registered. - * - * 1) The preferred method is by means of class annotations in the OpMode itself. - * See, for example the class annotations in {@link org.firstinspires.ftc.robotcontroller.external.samples.ConceptNullOp}. - * - * 2) The other, retired, method is to modify this {@link #register(OpModeManager)} - * method to include explicit calls to OpModeManager.register(). - * This method of modifying this file directly is discouraged, as it - * makes updates to the SDK harder to integrate into your code. - * - * @param manager the object which contains methods for carrying out OpMode registrations - * - * @see com.qualcomm.robotcore.eventloop.opmode.TeleOp - * @see com.qualcomm.robotcore.eventloop.opmode.Autonomous - */ - public void register(OpModeManager manager) { - - /** - * Any manual OpMode class registrations should go here. - */ - } -} diff --git a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/internal/FtcRobotControllerActivity.java b/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/internal/FtcRobotControllerActivity.java deleted file mode 100644 index 3f1f77c..0000000 --- a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/internal/FtcRobotControllerActivity.java +++ /dev/null @@ -1,845 +0,0 @@ -/* Copyright (c) 2014, 2015 Qualcomm Technologies Inc - -All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted (subject to the limitations in the disclaimer below) provided that -the following conditions are met: - -Redistributions of source code must retain the above copyright notice, this list -of conditions and the following disclaimer. - -Redistributions in binary form must reproduce the above copyright notice, this -list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. - -Neither the name of Qualcomm Technologies Inc nor the names of its contributors -may be used to endorse or promote products derived from this software without -specific prior written permission. - -NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS -LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ - -package org.firstinspires.ftc.robotcontroller.internal; - -import android.app.ActionBar; -import android.app.Activity; -import android.app.ActivityManager; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.ServiceConnection; -import android.content.SharedPreferences; -import android.content.res.Configuration; -import android.hardware.usb.UsbDevice; -import android.hardware.usb.UsbManager; -import android.net.wifi.WifiManager; -import android.os.Bundle; -import android.os.IBinder; -import android.preference.PreferenceManager; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.StringRes; -import android.view.Menu; -import android.view.MenuItem; -import android.view.MotionEvent; -import android.view.View; -import android.view.WindowManager; -import android.webkit.WebView; -import android.widget.ImageButton; -import android.widget.LinearLayout; -import android.widget.LinearLayout.LayoutParams; -import android.widget.PopupMenu; -import android.widget.TextView; - -import com.google.blocks.ftcrobotcontroller.ProgrammingWebHandlers; -import com.google.blocks.ftcrobotcontroller.runtime.BlocksOpMode; -import com.qualcomm.ftccommon.ClassManagerFactory; -import com.qualcomm.ftccommon.FtcAboutActivity; -import com.qualcomm.ftccommon.FtcEventLoop; -import com.qualcomm.ftccommon.FtcEventLoopIdle; -import com.qualcomm.ftccommon.FtcRobotControllerService; -import com.qualcomm.ftccommon.FtcRobotControllerService.FtcRobotControllerBinder; -import com.qualcomm.ftccommon.FtcRobotControllerSettingsActivity; -import com.qualcomm.ftccommon.LaunchActivityConstantsList; -import com.qualcomm.ftccommon.LaunchActivityConstantsList.RequestCode; -import com.qualcomm.ftccommon.Restarter; -import com.qualcomm.ftccommon.UpdateUI; -import com.qualcomm.ftccommon.configuration.EditParameters; -import com.qualcomm.ftccommon.configuration.FtcLoadFileActivity; -import com.qualcomm.ftccommon.configuration.RobotConfigFile; -import com.qualcomm.ftccommon.configuration.RobotConfigFileManager; -import com.qualcomm.ftcrobotcontroller.BuildConfig; -import com.qualcomm.ftcrobotcontroller.R; -import com.qualcomm.hardware.HardwareFactory; -import com.qualcomm.robotcore.eventloop.EventLoopManager; -import com.qualcomm.robotcore.eventloop.opmode.FtcRobotControllerServiceState; -import com.qualcomm.robotcore.eventloop.opmode.OpModeRegister; -import com.qualcomm.robotcore.hardware.configuration.LynxConstants; -import com.qualcomm.robotcore.hardware.configuration.Utility; -import com.qualcomm.robotcore.robot.Robot; -import com.qualcomm.robotcore.robot.RobotState; -import com.qualcomm.robotcore.util.ClockWarningSource; -import com.qualcomm.robotcore.util.Device; -import com.qualcomm.robotcore.util.Dimmer; -import com.qualcomm.robotcore.util.ImmersiveMode; -import com.qualcomm.robotcore.util.RobotLog; -import com.qualcomm.robotcore.util.WebServer; -import com.qualcomm.robotcore.wifi.NetworkConnection; -import com.qualcomm.robotcore.wifi.NetworkConnectionFactory; -import com.qualcomm.robotcore.wifi.NetworkType; - -import org.firstinspires.ftc.ftccommon.external.SoundPlayingRobotMonitor; -import org.firstinspires.ftc.ftccommon.internal.AnnotatedHooksClassFilter; -import org.firstinspires.ftc.ftccommon.internal.FtcRobotControllerWatchdogService; -import org.firstinspires.ftc.ftccommon.internal.ProgramAndManageActivity; -import org.firstinspires.ftc.onbotjava.ExternalLibraries; -import org.firstinspires.ftc.onbotjava.OnBotJavaHelperImpl; -import org.firstinspires.ftc.onbotjava.OnBotJavaProgrammingMode; -import org.firstinspires.ftc.robotcore.external.navigation.MotionDetection; -import org.firstinspires.ftc.robotcore.internal.hardware.android.AndroidBoard; -import org.firstinspires.ftc.robotcore.internal.network.DeviceNameManagerFactory; -import org.firstinspires.ftc.robotcore.internal.network.PreferenceRemoterRC; -import org.firstinspires.ftc.robotcore.internal.network.StartResult; -import org.firstinspires.ftc.robotcore.internal.network.WifiDirectChannelChanger; -import org.firstinspires.ftc.robotcore.internal.network.WifiMuteEvent; -import org.firstinspires.ftc.robotcore.internal.network.WifiMuteStateMachine; -import org.firstinspires.ftc.robotcore.internal.opmode.ClassManager; -import org.firstinspires.ftc.robotcore.internal.opmode.OnBotJavaHelper; -import org.firstinspires.ftc.robotcore.internal.system.AppAliveNotifier; -import org.firstinspires.ftc.robotcore.internal.system.AppUtil; -import org.firstinspires.ftc.robotcore.internal.system.Assert; -import org.firstinspires.ftc.robotcore.internal.system.PreferencesHelper; -import org.firstinspires.ftc.robotcore.internal.system.ServiceController; -import org.firstinspires.ftc.robotcore.internal.ui.ThemedActivity; -import org.firstinspires.ftc.robotcore.internal.ui.UILocation; -import org.firstinspires.ftc.robotcore.internal.webserver.RobotControllerWebInfo; -import org.firstinspires.ftc.robotserver.internal.programmingmode.ProgrammingModeManager; -import org.firstinspires.inspection.RcInspectionActivity; -import org.threeten.bp.YearMonth; -import org.xmlpull.v1.XmlPullParserException; - -import java.io.FileNotFoundException; -import java.util.List; -import java.util.Queue; -import java.util.concurrent.ConcurrentLinkedQueue; - -@SuppressWarnings("WeakerAccess") -public class FtcRobotControllerActivity extends Activity - { - public static final String TAG = "RCActivity"; - public String getTag() { return TAG; } - - private static final int REQUEST_CONFIG_WIFI_CHANNEL = 1; - private static final int NUM_GAMEPADS = 2; - - protected WifiManager.WifiLock wifiLock; - protected RobotConfigFileManager cfgFileMgr; - - private OnBotJavaHelper onBotJavaHelper; - - protected ProgrammingModeManager programmingModeManager; - - protected UpdateUI.Callback callback; - protected Context context; - protected Utility utility; - protected StartResult prefRemoterStartResult = new StartResult(); - protected StartResult deviceNameStartResult = new StartResult(); - protected PreferencesHelper preferencesHelper; - protected final SharedPreferencesListener sharedPreferencesListener = new SharedPreferencesListener(); - - protected ImageButton buttonMenu; - protected TextView textDeviceName; - protected TextView textNetworkConnectionStatus; - protected TextView textRobotStatus; - protected TextView[] textGamepad = new TextView[NUM_GAMEPADS]; - protected TextView textOpMode; - protected TextView textErrorMessage; - protected ImmersiveMode immersion; - - protected UpdateUI updateUI; - protected Dimmer dimmer; - protected LinearLayout entireScreenLayout; - - protected FtcRobotControllerService controllerService; - protected NetworkType networkType; - - protected FtcEventLoop eventLoop; - protected Queue receivedUsbAttachmentNotifications; - - protected WifiMuteStateMachine wifiMuteStateMachine; - protected MotionDetection motionDetection; - - private static boolean permissionsValidated = false; - - private WifiDirectChannelChanger wifiDirectChannelChanger; - - protected class RobotRestarter implements Restarter { - - public void requestRestart() { - requestRobotRestart(); - } - - } - - protected boolean serviceShouldUnbind = false; - protected ServiceConnection connection = new ServiceConnection() { - @Override - public void onServiceConnected(ComponentName name, IBinder service) { - FtcRobotControllerBinder binder = (FtcRobotControllerBinder) service; - onServiceBind(binder.getService()); - } - - @Override - public void onServiceDisconnected(ComponentName name) { - RobotLog.vv(FtcRobotControllerService.TAG, "%s.controllerService=null", TAG); - controllerService = null; - } - }; - - @Override - protected void onNewIntent(Intent intent) { - super.onNewIntent(intent); - - if (UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(intent.getAction())) { - UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); - RobotLog.vv(TAG, "ACTION_USB_DEVICE_ATTACHED: %s", usbDevice.getDeviceName()); - - if (usbDevice != null) { // paranoia - // We might get attachment notifications before the event loop is set up, so - // we hold on to them and pass them along only when we're good and ready. - if (receivedUsbAttachmentNotifications != null) { // *total* paranoia - receivedUsbAttachmentNotifications.add(usbDevice); - passReceivedUsbAttachmentsToEventLoop(); - } - } - } - } - - protected void passReceivedUsbAttachmentsToEventLoop() { - if (this.eventLoop != null) { - for (;;) { - UsbDevice usbDevice = receivedUsbAttachmentNotifications.poll(); - if (usbDevice == null) - break; - this.eventLoop.onUsbDeviceAttached(usbDevice); - } - } - else { - // Paranoia: we don't want the pending list to grow without bound when we don't - // (yet) have an event loop - while (receivedUsbAttachmentNotifications.size() > 100) { - receivedUsbAttachmentNotifications.poll(); - } - } - } - - /** - * There are cases where a permission may be revoked and the system restart will restart the - * FtcRobotControllerActivity, instead of the launch activity. Detect when that happens, and throw - * the device back to the permission validator activity. - */ - protected boolean enforcePermissionValidator() { - if (!permissionsValidated) { - RobotLog.vv(TAG, "Redirecting to permission validator"); - Intent permissionValidatorIntent = new Intent(AppUtil.getDefContext(), PermissionValidatorWrapper.class); - startActivity(permissionValidatorIntent); - finish(); - return true; - } else { - RobotLog.vv(TAG, "Permissions validated already"); - return false; - } - } - - public static void setPermissionsValidated() { - permissionsValidated = true; - } - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - if (enforcePermissionValidator()) { - return; - } - - RobotLog.onApplicationStart(); // robustify against onCreate() following onDestroy() but using the same app instance, which apparently does happen - RobotLog.vv(TAG, "onCreate()"); - ThemedActivity.appAppThemeToActivity(getTag(), this); // do this way instead of inherit to help AppInventor - - // Oddly, sometimes after a crash & restart the root activity will be something unexpected, like from the before crash? We don't yet understand - RobotLog.vv(TAG, "rootActivity is of class %s", AppUtil.getInstance().getRootActivity().getClass().getSimpleName()); - RobotLog.vv(TAG, "launchActivity is of class %s", FtcRobotControllerWatchdogService.launchActivity()); - Assert.assertTrue(FtcRobotControllerWatchdogService.isLaunchActivity(AppUtil.getInstance().getRootActivity())); - Assert.assertTrue(AppUtil.getInstance().isRobotController()); - - // Quick check: should we pretend we're not here, and so allow the Lynx to operate as - // a stand-alone USB-connected module? - if (LynxConstants.isRevControlHub()) { - // Double-sure check that we can talk to the DB over the serial TTY - AndroidBoard.getInstance().getAndroidBoardIsPresentPin().setState(true); - } - - context = this; - utility = new Utility(this); - - DeviceNameManagerFactory.getInstance().start(deviceNameStartResult); - - PreferenceRemoterRC.getInstance().start(prefRemoterStartResult); - - receivedUsbAttachmentNotifications = new ConcurrentLinkedQueue(); - eventLoop = null; - - setContentView(R.layout.activity_ftc_controller); - - preferencesHelper = new PreferencesHelper(TAG, context); - preferencesHelper.writeBooleanPrefIfDifferent(context.getString(R.string.pref_rc_connected), true); - preferencesHelper.getSharedPreferences().registerOnSharedPreferenceChangeListener(sharedPreferencesListener); - - // Check if this RC app is from a later FTC season than what was installed previously - int ftcSeasonYearOfPreviouslyInstalledRc = preferencesHelper.readInt(getString(R.string.pref_ftc_season_year_of_current_rc), 0); - int ftcSeasonYearOfCurrentlyInstalledRc = AppUtil.getInstance().getFtcSeasonYear(AppUtil.getInstance().getLocalSdkBuildMonth()).getValue(); - if (ftcSeasonYearOfCurrentlyInstalledRc > ftcSeasonYearOfPreviouslyInstalledRc) { - preferencesHelper.writeIntPrefIfDifferent(getString(R.string.pref_ftc_season_year_of_current_rc), ftcSeasonYearOfCurrentlyInstalledRc); - // Since it's a new FTC season, we should reset certain settings back to their default values. - preferencesHelper.writeBooleanPrefIfDifferent(getString(R.string.pref_warn_about_2_4_ghz_band), true); - preferencesHelper.writeBooleanPrefIfDifferent(getString(R.string.pref_warn_about_obsolete_software), true); - preferencesHelper.writeBooleanPrefIfDifferent(getString(R.string.pref_warn_about_mismatched_app_versions), true); - preferencesHelper.writeBooleanPrefIfDifferent(getString(R.string.pref_warn_about_incorrect_clocks), true); - } - - entireScreenLayout = (LinearLayout) findViewById(R.id.entire_screen); - buttonMenu = (ImageButton) findViewById(R.id.menu_buttons); - buttonMenu.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - PopupMenu popupMenu = new PopupMenu(FtcRobotControllerActivity.this, v); - popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() { - @Override - public boolean onMenuItemClick(MenuItem item) { - return onOptionsItemSelected(item); // Delegate to the handler for the hardware menu button - } - }); - popupMenu.inflate(R.menu.ftc_robot_controller); - AnnotatedHooksClassFilter.getInstance().callOnCreateMenuMethods( - FtcRobotControllerActivity.this, popupMenu.getMenu()); - popupMenu.show(); - } - }); - - updateMonitorLayout(getResources().getConfiguration()); - - BlocksOpMode.setActivityAndWebView(this, (WebView) findViewById(R.id.webViewBlocksRuntime)); - - ExternalLibraries.getInstance().onCreate(); - onBotJavaHelper = new OnBotJavaHelperImpl(); - - /* - * Paranoia as the ClassManagerFactory requires EXTERNAL_STORAGE permissions - * and we've seen on the DS where the finish() call above does not short-circuit - * the onCreate() call for the activity and then we crash here because we don't - * have permissions. So... - */ - if (permissionsValidated) { - ClassManager.getInstance().setOnBotJavaClassHelper(onBotJavaHelper); - ClassManagerFactory.registerFilters(); - ClassManagerFactory.processAllClasses(); - } - - cfgFileMgr = new RobotConfigFileManager(this); - - // Clean up 'dirty' status after a possible crash - RobotConfigFile configFile = cfgFileMgr.getActiveConfig(); - if (configFile.isDirty()) { - configFile.markClean(); - cfgFileMgr.setActiveConfig(false, configFile); - } - - textDeviceName = (TextView) findViewById(R.id.textDeviceName); - textNetworkConnectionStatus = (TextView) findViewById(R.id.textNetworkConnectionStatus); - textRobotStatus = (TextView) findViewById(R.id.textRobotStatus); - textOpMode = (TextView) findViewById(R.id.textOpMode); - textErrorMessage = (TextView) findViewById(R.id.textErrorMessage); - textGamepad[0] = (TextView) findViewById(R.id.textGamepad1); - textGamepad[1] = (TextView) findViewById(R.id.textGamepad2); - immersion = new ImmersiveMode(getWindow().getDecorView()); - dimmer = new Dimmer(this); - dimmer.longBright(); - - programmingModeManager = new ProgrammingModeManager(); - programmingModeManager.register(new ProgrammingWebHandlers()); - programmingModeManager.register(new OnBotJavaProgrammingMode()); - - updateUI = createUpdateUI(); - callback = createUICallback(updateUI); - - PreferenceManager.setDefaultValues(this, R.xml.app_settings, false); - - WifiManager wifiManager = (WifiManager) getApplicationContext().getSystemService(Context.WIFI_SERVICE); - wifiLock = wifiManager.createWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF, ""); - - hittingMenuButtonBrightensScreen(); - - wifiLock.acquire(); - callback.networkConnectionUpdate(NetworkConnection.NetworkEvent.DISCONNECTED); - readNetworkType(); - ServiceController.startService(FtcRobotControllerWatchdogService.class); - bindToService(); - RobotLog.logAppInfo(); - RobotLog.logDeviceInfo(); - AndroidBoard.getInstance().logAndroidBoardInfo(); - - if (preferencesHelper.readBoolean(getString(R.string.pref_wifi_automute), false)) { - initWifiMute(true); - } - - FtcAboutActivity.setBuildTimeFromBuildConfig(BuildConfig.APP_BUILD_TIME); - - // check to see if there is a preferred Wi-Fi to use. - checkPreferredChannel(); - - AnnotatedHooksClassFilter.getInstance().callOnCreateMethods(this); - } - - protected UpdateUI createUpdateUI() { - Restarter restarter = new RobotRestarter(); - UpdateUI result = new UpdateUI(this, dimmer); - result.setRestarter(restarter); - result.setTextViews(textNetworkConnectionStatus, textRobotStatus, textGamepad, textOpMode, textErrorMessage, textDeviceName); - return result; - } - - protected UpdateUI.Callback createUICallback(UpdateUI updateUI) { - UpdateUI.Callback result = updateUI.new Callback(); - result.setStateMonitor(new SoundPlayingRobotMonitor()); - return result; - } - - @Override - protected void onStart() { - super.onStart(); - RobotLog.vv(TAG, "onStart()"); - - entireScreenLayout.setOnTouchListener(new View.OnTouchListener() { - @Override - public boolean onTouch(View v, MotionEvent event) { - dimmer.handleDimTimer(); - return false; - } - }); - } - - @Override - protected void onResume() { - super.onResume(); - RobotLog.vv(TAG, "onResume()"); - - // In case the user just got back from fixing their clock, refresh ClockWarningSource - ClockWarningSource.getInstance().onPossibleRcClockUpdate(); - } - - @Override - protected void onPause() { - super.onPause(); - RobotLog.vv(TAG, "onPause()"); - } - - @Override - protected void onStop() { - // Note: this gets called even when the configuration editor is launched. That is, it gets - // called surprisingly often. So, we don't actually do much here. - super.onStop(); - RobotLog.vv(TAG, "onStop()"); - } - - @Override - protected void onDestroy() { - super.onDestroy(); - RobotLog.vv(TAG, "onDestroy()"); - - shutdownRobot(); // Ensure the robot is put away to bed - if (callback != null) callback.close(); - - PreferenceRemoterRC.getInstance().stop(prefRemoterStartResult); - DeviceNameManagerFactory.getInstance().stop(deviceNameStartResult); - - unbindFromService(); - // If the app manually (?) is stopped, then we don't need the auto-starting function (?) - ServiceController.stopService(FtcRobotControllerWatchdogService.class); - if (wifiLock != null) wifiLock.release(); - if (preferencesHelper != null) preferencesHelper.getSharedPreferences().unregisterOnSharedPreferenceChangeListener(sharedPreferencesListener); - - RobotLog.cancelWriteLogcatToDisk(); - - AnnotatedHooksClassFilter.getInstance().callOnDestroyMethods(this); - } - - protected void bindToService() { - readNetworkType(); - Intent intent = new Intent(this, FtcRobotControllerService.class); - intent.putExtra(NetworkConnectionFactory.NETWORK_CONNECTION_TYPE, networkType); - serviceShouldUnbind = bindService(intent, connection, Context.BIND_AUTO_CREATE); - } - - protected void unbindFromService() { - if (serviceShouldUnbind) { - unbindService(connection); - serviceShouldUnbind = false; - } - } - - protected void readNetworkType() { - // Control hubs are always running the access point model. Everything else, for the time - // being always runs the Wi-Fi Direct model. - if (Device.isRevControlHub() == true) { - networkType = NetworkType.RCWIRELESSAP; - } else { - networkType = NetworkType.fromString(preferencesHelper.readString(context.getString(R.string.pref_pairing_kind), NetworkType.globalDefaultAsString())); - } - - // update the app_settings - preferencesHelper.writeStringPrefIfDifferent(context.getString(R.string.pref_pairing_kind), networkType.toString()); - } - - @Override - public void onWindowFocusChanged(boolean hasFocus) { - super.onWindowFocusChanged(hasFocus); - - if (hasFocus) { - immersion.hideSystemUI(); - getWindow().setFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION, WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION); - } - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - getMenuInflater().inflate(R.menu.ftc_robot_controller, menu); - AnnotatedHooksClassFilter.getInstance().callOnCreateMenuMethods(this, menu); - return true; - } - - private boolean isRobotRunning() { - if (controllerService == null) { - return false; - } - - Robot robot = controllerService.getRobot(); - - if ((robot == null) || (robot.eventLoopManager == null)) { - return false; - } - - RobotState robotState = robot.eventLoopManager.state; - - if (robotState != RobotState.RUNNING) { - return false; - } else { - return true; - } - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - int id = item.getItemId(); - - if (id == R.id.action_program_and_manage) { - if (isRobotRunning()) { - Intent programmingModeIntent = new Intent(AppUtil.getDefContext(), ProgramAndManageActivity.class); - RobotControllerWebInfo webInfo = programmingModeManager.getWebServer().getConnectionInformation(); - programmingModeIntent.putExtra(LaunchActivityConstantsList.RC_WEB_INFO, webInfo.toJson()); - startActivity(programmingModeIntent); - } else { - AppUtil.getInstance().showToast(UILocation.ONLY_LOCAL, context.getString(R.string.toastWifiUpBeforeProgrammingMode)); - } - } else if (id == R.id.action_inspection_mode) { - Intent inspectionModeIntent = new Intent(AppUtil.getDefContext(), RcInspectionActivity.class); - startActivity(inspectionModeIntent); - return true; - } else if (id == R.id.action_restart_robot) { - dimmer.handleDimTimer(); - AppUtil.getInstance().showToast(UILocation.BOTH, context.getString(R.string.toastRestartingRobot)); - requestRobotRestart(); - return true; - } - else if (id == R.id.action_configure_robot) { - EditParameters parameters = new EditParameters(); - Intent intentConfigure = new Intent(AppUtil.getDefContext(), FtcLoadFileActivity.class); - parameters.putIntent(intentConfigure); - startActivityForResult(intentConfigure, RequestCode.CONFIGURE_ROBOT_CONTROLLER.ordinal()); - } - else if (id == R.id.action_settings) { - // historical: this once erroneously used FTC_CONFIGURE_REQUEST_CODE_ROBOT_CONTROLLER - Intent settingsIntent = new Intent(AppUtil.getDefContext(), FtcRobotControllerSettingsActivity.class); - startActivityForResult(settingsIntent, RequestCode.SETTINGS_ROBOT_CONTROLLER.ordinal()); - return true; - } - else if (id == R.id.action_about) { - Intent intent = new Intent(AppUtil.getDefContext(), FtcAboutActivity.class); - startActivity(intent); - return true; - } - else if (id == R.id.action_exit_app) { - - //Clear backstack and everything to prevent edge case where VM might be - //restarted (after it was exited) if more than one activity was on the - //backstack for some reason. - finishAffinity(); - - //For lollipop and up, we can clear ourselves from the recents list too - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) { - ActivityManager manager = (ActivityManager) getSystemService(ACTIVITY_SERVICE); - List tasks = manager.getAppTasks(); - - for (ActivityManager.AppTask task : tasks) { - task.finishAndRemoveTask(); - } - } - - // Allow the user to use the Control Hub operating system's UI, instead of relaunching the app - AppAliveNotifier.getInstance().disableAppWatchdogUntilNextAppStart(); - - //Finally, nuke the VM from orbit - AppUtil.getInstance().exitApplication(); - - return true; - } - - return super.onOptionsItemSelected(item); - } - - @Override - public void onConfigurationChanged(Configuration newConfig) { - super.onConfigurationChanged(newConfig); - // don't destroy assets on screen rotation - updateMonitorLayout(newConfig); - } - - /** - * Updates the orientation of monitorContainer (which contains cameraMonitorView) - * based on the given configuration. Makes the children split the space. - */ - private void updateMonitorLayout(Configuration configuration) { - LinearLayout monitorContainer = (LinearLayout) findViewById(R.id.monitorContainer); - if (configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) { - // When the phone is landscape, lay out the monitor views horizontally. - monitorContainer.setOrientation(LinearLayout.HORIZONTAL); - for (int i = 0; i < monitorContainer.getChildCount(); i++) { - View view = monitorContainer.getChildAt(i); - view.setLayoutParams(new LayoutParams(0, LayoutParams.MATCH_PARENT, 1 /* weight */)); - } - } else { - // When the phone is portrait, lay out the monitor views vertically. - monitorContainer.setOrientation(LinearLayout.VERTICAL); - for (int i = 0; i < monitorContainer.getChildCount(); i++) { - View view = monitorContainer.getChildAt(i); - view.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, 0, 1 /* weight */)); - } - } - monitorContainer.requestLayout(); - } - - @Override - protected void onActivityResult(int request, int result, Intent intent) { - if (request == REQUEST_CONFIG_WIFI_CHANNEL) { - if (result == RESULT_OK) { - AppUtil.getInstance().showToast(UILocation.BOTH, context.getString(R.string.toastWifiConfigurationComplete)); - } - } - // was some historical confusion about launch codes here, so we err safely - if (request == RequestCode.CONFIGURE_ROBOT_CONTROLLER.ordinal() || request == RequestCode.SETTINGS_ROBOT_CONTROLLER.ordinal()) { - // We always do a refresh, whether it was a cancel or an OK, for robustness - shutdownRobot(); - cfgFileMgr.getActiveConfigAndUpdateUI(); - updateUIAndRequestRobotSetup(); - } - } - - public void onServiceBind(final FtcRobotControllerService service) { - RobotLog.vv(FtcRobotControllerService.TAG, "%s.controllerService=bound", TAG); - controllerService = service; - updateUI.setControllerService(controllerService); - - controllerService.setOnBotJavaHelper(onBotJavaHelper); - - updateUIAndRequestRobotSetup(); - programmingModeManager.setState(new FtcRobotControllerServiceState() { - @NonNull - @Override - public WebServer getWebServer() { - return service.getWebServer(); - } - - @Nullable - @Override - public OnBotJavaHelper getOnBotJavaHelper() { - return service.getOnBotJavaHelper(); - } - - @Override - public EventLoopManager getEventLoopManager() { - return service.getRobot().eventLoopManager; - } - }); - - AnnotatedHooksClassFilter.getInstance().callWebHandlerRegistrarMethods(this, - service.getWebServer().getWebHandlerManager()); - } - - private void updateUIAndRequestRobotSetup() { - if (controllerService != null) { - callback.networkConnectionUpdate(controllerService.getNetworkConnectionStatus()); - callback.updateRobotStatus(controllerService.getRobotStatus()); - // Only show this first-time toast on headless systems: what we have now on non-headless suffices - requestRobotSetup(LynxConstants.isRevControlHub() - ? new Runnable() { - @Override public void run() { - showRestartRobotCompleteToast(R.string.toastRobotSetupComplete); - } - } - : null); - } - } - - private void requestRobotSetup(@Nullable Runnable runOnComplete) { - if (controllerService == null) return; - - RobotConfigFile file = cfgFileMgr.getActiveConfigAndUpdateUI(); - HardwareFactory hardwareFactory = new HardwareFactory(context); - try { - hardwareFactory.setXmlPullParser(file.getXml()); - } catch (FileNotFoundException | XmlPullParserException e) { - RobotLog.ww(TAG, e, "Unable to set configuration file %s. Falling back on noConfig.", file.getName()); - file = RobotConfigFile.noConfig(cfgFileMgr); - try { - hardwareFactory.setXmlPullParser(file.getXml()); - cfgFileMgr.setActiveConfigAndUpdateUI(false, file); - } catch (FileNotFoundException | XmlPullParserException e1) { - RobotLog.ee(TAG, e1, "Failed to fall back on noConfig"); - } - } - - OpModeRegister userOpModeRegister = createOpModeRegister(); - eventLoop = new FtcEventLoop(hardwareFactory, userOpModeRegister, callback, this); - FtcEventLoopIdle idleLoop = new FtcEventLoopIdle(hardwareFactory, userOpModeRegister, callback, this); - - controllerService.setCallback(callback); - controllerService.setupRobot(eventLoop, idleLoop, runOnComplete); - - passReceivedUsbAttachmentsToEventLoop(); - AndroidBoard.showErrorIfUnknownControlHub(); - - AnnotatedHooksClassFilter.getInstance().callOnCreateEventLoopMethods(this, eventLoop); - } - - protected OpModeRegister createOpModeRegister() { - return new FtcOpModeRegister(); - } - - private void shutdownRobot() { - if (controllerService != null) controllerService.shutdownRobot(); - } - - private void requestRobotRestart() { - AppUtil.getInstance().showToast(UILocation.BOTH, AppUtil.getDefContext().getString(R.string.toastRestartingRobot)); - // - RobotLog.clearGlobalErrorMsg(); - RobotLog.clearGlobalWarningMsg(); - shutdownRobot(); - requestRobotSetup(new Runnable() { - @Override public void run() { - showRestartRobotCompleteToast(R.string.toastRestartRobotComplete); - } - }); - } - - private void showRestartRobotCompleteToast(@StringRes int resid) { - AppUtil.getInstance().showToast(UILocation.BOTH, AppUtil.getDefContext().getString(resid)); - } - - private void checkPreferredChannel() { - // For P2P network, check to see what preferred channel is. - if (networkType == NetworkType.WIFIDIRECT) { - int prefChannel = preferencesHelper.readInt(getString(com.qualcomm.ftccommon.R.string.pref_wifip2p_channel), -1); - if (prefChannel == -1) { - prefChannel = 0; - RobotLog.vv(TAG, "pref_wifip2p_channel: No preferred channel defined. Will use a default value of %d", prefChannel); - } else { - RobotLog.vv(TAG, "pref_wifip2p_channel: Found existing preferred channel (%d).", prefChannel); - } - - // attempt to set the preferred channel. - RobotLog.vv(TAG, "pref_wifip2p_channel: attempting to set preferred channel..."); - wifiDirectChannelChanger = new WifiDirectChannelChanger(); - wifiDirectChannelChanger.changeToChannel(prefChannel); - } - } - - protected void hittingMenuButtonBrightensScreen() { - ActionBar actionBar = getActionBar(); - if (actionBar != null) { - actionBar.addOnMenuVisibilityListener(new ActionBar.OnMenuVisibilityListener() { - @Override - public void onMenuVisibilityChanged(boolean isVisible) { - if (isVisible) { - dimmer.handleDimTimer(); - } - } - }); - } - } - - protected class SharedPreferencesListener implements SharedPreferences.OnSharedPreferenceChangeListener { - @Override public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { - if (key.equals(context.getString(R.string.pref_app_theme))) { - ThemedActivity.restartForAppThemeChange(getTag(), getString(R.string.appThemeChangeRestartNotifyRC)); - } else if (key.equals(context.getString(R.string.pref_wifi_automute))) { - if (preferencesHelper.readBoolean(context.getString(R.string.pref_wifi_automute), false)) { - initWifiMute(true); - } else { - initWifiMute(false); - } - } - } - } - - protected void initWifiMute(boolean enable) { - if (enable) { - wifiMuteStateMachine = new WifiMuteStateMachine(); - wifiMuteStateMachine.initialize(); - wifiMuteStateMachine.start(); - - motionDetection = new MotionDetection(2.0, 10); - motionDetection.startListening(); - motionDetection.registerListener(new MotionDetection.MotionDetectionListener() { - @Override - public void onMotionDetected(double vector) - { - wifiMuteStateMachine.consumeEvent(WifiMuteEvent.USER_ACTIVITY); - } - }); - } else { - wifiMuteStateMachine.stop(); - wifiMuteStateMachine = null; - motionDetection.stopListening(); - motionDetection.purgeListeners(); - motionDetection = null; - } - } - - @Override - public void onUserInteraction() { - if (wifiMuteStateMachine != null) { - wifiMuteStateMachine.consumeEvent(WifiMuteEvent.USER_ACTIVITY); - } - } -} diff --git a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/internal/PermissionValidatorWrapper.java b/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/internal/PermissionValidatorWrapper.java deleted file mode 100644 index a0094bc..0000000 --- a/examples/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/internal/PermissionValidatorWrapper.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright (c) 2018 Craig MacFarlane - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted - * (subject to the limitations in the disclaimer below) provided that the following conditions are - * met: - * - * Redistributions of source code must retain the above copyright notice, this list of conditions - * and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this list of conditions - * and the following disclaimer in the documentation and/or other materials provided with the - * distribution. - * - * Neither the name of Craig MacFarlane nor the names of its contributors may be used to - * endorse or promote products derived from this software without specific prior written permission. - * - * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS LICENSE. THIS - * SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS - * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, - * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF - * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package org.firstinspires.ftc.robotcontroller.internal; - -import android.Manifest; -import android.os.Bundle; - -import com.qualcomm.ftcrobotcontroller.R; - -import org.firstinspires.ftc.robotcore.internal.system.Misc; -import org.firstinspires.ftc.robotcore.internal.system.PermissionValidatorActivity; - -import java.util.ArrayList; -import java.util.List; - -public class PermissionValidatorWrapper extends PermissionValidatorActivity { - - private final String TAG = "PermissionValidatorWrapper"; - - /* - * The list of dangerous permissions the robot controller needs. - */ - protected List robotControllerPermissions = new ArrayList() {{ - add(Manifest.permission.WRITE_EXTERNAL_STORAGE); - add(Manifest.permission.READ_EXTERNAL_STORAGE); - add(Manifest.permission.CAMERA); - add(Manifest.permission.ACCESS_COARSE_LOCATION); - add(Manifest.permission.ACCESS_FINE_LOCATION); - add(Manifest.permission.READ_PHONE_STATE); - }}; - - private final static Class startApplication = FtcRobotControllerActivity.class; - - public String mapPermissionToExplanation(final String permission) { - if (permission.equals(Manifest.permission.WRITE_EXTERNAL_STORAGE)) { - return Misc.formatForUser(R.string.permRcWriteExternalStorageExplain); - } else if (permission.equals(Manifest.permission.READ_EXTERNAL_STORAGE)) { - return Misc.formatForUser(R.string.permRcReadExternalStorageExplain); - } else if (permission.equals(Manifest.permission.CAMERA)) { - return Misc.formatForUser(R.string.permRcCameraExplain); - } else if (permission.equals(Manifest.permission.ACCESS_COARSE_LOCATION)) { - return Misc.formatForUser(R.string.permAccessLocationExplain); - } else if (permission.equals(Manifest.permission.ACCESS_FINE_LOCATION)) { - return Misc.formatForUser(R.string.permAccessLocationExplain); - } else if (permission.equals(Manifest.permission.READ_PHONE_STATE)) { - return Misc.formatForUser(R.string.permReadPhoneState); - } - return Misc.formatForUser(R.string.permGenericExplain); - } - - @Override - protected void onCreate(Bundle savedInstanceState) - { - super.onCreate(savedInstanceState); - - permissions = robotControllerPermissions; - } - - protected Class onStartApplication() - { - FtcRobotControllerActivity.setPermissionsValidated(); - return startApplication; - } -} diff --git a/examples/FtcRobotController/src/main/res/drawable-xhdpi/icon_menu.png b/examples/FtcRobotController/src/main/res/drawable-xhdpi/icon_menu.png deleted file mode 100644 index 6b9e997c5624fad049a7192d54034336f5216eaa..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 975 zcmaJ=&ui0A91l2#lnrl!!r+mCOj+|@vb9~p;^^|4t!PT=I_)m9G#pWdU$RplDe*&&7KHhQB($9^!a$R3dVwx}KYA+y&(+2IISW~#L0aB&Cn77IO5N#f zIjSoz+y?WB#tD4FY>@cdL98XZ*yYvuIlYD==(?~iT|7^!VO4=aBLZ?#KIkKb4NO_K;|1yE%`VEav~mzLJ8(!D>muioJkQlktRkF8ra7k=vRs-iE*7!L?Zr|q zUo=#?kHys4@kzk?Sa*Px(NJtE2tVYJF^RlK#5E)8gKpuPH#`?Sl&^<%hvtGxL$Q!2 z1Jm*THSYcu*HC&Kh?g4!`ICTaKH}nj-#mN6ABJAW#d>n~?W3F#z}Uz!{`Gr5_jnqA zUORkw1MD9>KQZ@4-u-yB1}Daz{yaHcMcW7Kr}vK0-lwbUg8E?e#p==5Tj>)kmn!)6 H;`)<6M=di_ diff --git a/examples/FtcRobotController/src/main/res/drawable-xhdpi/icon_robotcontroller.png b/examples/FtcRobotController/src/main/res/drawable-xhdpi/icon_robotcontroller.png deleted file mode 100644 index 022552f409c06b5c93aec2300324913fadcaac51..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4777 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 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/examples/FtcRobotController/src/main/res/menu/ftc_robot_controller.xml b/examples/FtcRobotController/src/main/res/menu/ftc_robot_controller.xml deleted file mode 100644 index 657c1aa..0000000 --- a/examples/FtcRobotController/src/main/res/menu/ftc_robot_controller.xml +++ /dev/null @@ -1,78 +0,0 @@ - - -

- - - - - - - - - - - - - - - diff --git a/examples/FtcRobotController/src/main/res/raw/gold.wav b/examples/FtcRobotController/src/main/res/raw/gold.wav deleted file mode 100644 index 3a7baf88159a373d490064e774d8796e02bcf0bd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 104016 zcmd43cTiJb6fSx~OX$5Rz4xjVK~St92q>T^im2EtV!?u9@4YK3f}(;53ZisWdI=;1 zNFj|t0%>#zCFiZ*{o~D@H}B5<=e?Obdm@l?&faUU^{sEMz4kt?3tqW$^-~P8VQI{Y zT?doRG!O*AATr2>QuswAgUBHo$j*f11S`152wjV!(n;xiX^FHNZ9>zf@1(=%97>n+ zq-+#HeBt^!bUXSBC7_*Bp;QquL}sO0=o|D0Dvw&D!RRhj12sUON3V5C zT7Z6$5~M58Q1l|YAFV*E&}j5L`a}9xdI^1i#!8RFJ!JH_^tv>dL4B^{b2O7Fpe*Lvk(nI_94SkR2qx~BZjCQ-17lVL!Y2CQVsMN zdJV;(2+BtV=u)^=g%+a2QZ=+6ZA0&(S?FTuMGG-UOi(XW53xjap~ps)jV?f!ql%~$ z|h!k~2)nEoLXenqT3w?^_pt)$fv{KrKQqgK@y)+s0 zAcOWv+0t_9IvC$qx&q|6h)zkds5P=2NkXI07wBcUYYS+6DLM{gt%fvxfxbr9LqAlMfo=thSi#)(A^VYPX^!+V zdKCUzh(3flCZkDk)fII60-cpkNLM1hh|~WVOM;$6kE7o~_7|X&dg*|42z0anbw{I+ z9S9bklNurGkk?=j5saaSEJDJe&x^=oBpoe5Un5VEUuYS+5Q#-zp!Y$}Kd3q4fII}9 zn8F-)g0>za&ydrgMNJs<3CQu^bs0nznGf>#BVI@s;B*%niw;T!QUno$J`2zj=mqG# z6MoADE3r`(u%;%o&`&UW1I+^)x1lUlg04XifdyhvdBhpvflrt~e`Juy z7J9e zHL?aY=7ekkpEW?{!JGq7D?|%^u7PWD;4^7x0n&~9ME{|>(61egu?p~W9=QmzFGCZc zUvKdEgUEH@DP6<>S&RrlL&pG_MljPjK>vPZBVeou6(KmxB+S(qV}v<~oJI;k?rNk9 z7-BzSh*^LsMdgrNNG@_2P`d{?h*ZH0e<6R7*T`F>867|aki+nE0`))=kaaNc|NKQN zRYrdU16cz;o&v@lz_zWxWjI71@ZBTTggH^r9(ZaPaFdTp0ipXq*8e!~D)h?*k9>=K zK_WnJVdxQbw{*93BX}JatQZQ`z5=K>1df>xGw%ieF$WCYfM*fJ2T<;hbOR;~!EQN# zwaZ8j5(an)2RSQ2_cdU{co;1keE%Er0Z~LkkP3u^u+a|m2f{}eBc;Lj*MK7y!K^Uw>`rtK`XA;j!G{b${#fwBbgIQy}K}FJE(3?B79<&8G7Y9CkA5dZiTHgh%n1D`7%^;%e zN4Ge!q02cG^qlNdsX@f=Cuc&StwmD^^!(Q&qy&+M4AF|OjbHFcMRrvO`0kF zBCV0Sp*ztH(j!tk(9c=vUFkK5m#)$XkX9KTkx~K8z0g-2S|%-!?gQMdkR}3;@*#c} zpzeS=ZNw3AfVpge&#ZJ>x)%8AC-~4w~Y%ueIgZ>}`2o6%CzladQA}o{(ev*#7fykbS`~yDuiWZ~ehzh0z z_@fJk?(A7qW3$75Uj)6~d z(O#5*^noXAh1st_`Vl=$2J#L5b;s1e7<_~b8Ie19<$mN5=)DK-e+_;s@>GBN^QvlAFA5j=bgaKXEs2atHodrUCMF(2cHIS=05gD7IoL5z5T zJc9XwW`QFbpdTf8(g5R#F^1ek3AA$&c%T`TL2Lm5XCM~L!|cJBA)62m;*4=ec0~0TYP11~!_5j8G5bkMV=p)PYEVe}_>4d?Sn4fZc2`B`|~gpr>N6 z$r50#GSJ3fh{A1PB^t!ydtlK~@cmz4i)7>>viUzu!!!F~77s8lG4aTD#0j$$^BnNI z26G5=5_l~KSUU)_3-cEFid@7z!>k7DuLqffK!zC1RZJ!F1EC-_phI;`4Cs~*a?L{A zCL-++d$7oRXw{JK1j8s0VIjM80bPs%=Qp7S7z0cjT;qb~?!dfzkO{ED1CU}1g27Ax z`ZvH#B#?hxKr4|tc-{me<2i`rUjJo9NDEj}5wubW_-sVS&@n^}Qw*{^0{bigM2!Fn zA0U?zAJiCK1|C@sV^u*v8W6pD!Q0Bgi(-HS!eOK=$bzzwQqYe)U~UIwc$*>Wxr292 zfSw1@+ekh_2HkxETN)r{5UYQIHj9x+h%b@gOOGLrF2HzVsOS{L!eW?p9x#3=L`iqx z1Yi~D=_h1%p`fD&;G?d9rJcx0$TiE*RPePv6bIfq1l%G<_X0ldq1ot1$h&eOPcVVF zDusA3E;WKkI}DkS9YnJnbT8z=eBf6D;0rTY4V(v$I0YG0JTOl!#6>+o!4zaJFM+oX z0Y|)m9`ArY6cBmvtXQN1BFQAG2)^(FY&3}00k<53oM#1K=Ns@~I^^Ja5I&mo&J zcQFHy=}3Xc9s{mTF&^NIOTo^efZaOCeLErNiU|2qSG?}6Eh(FPr7BP?VCW*=rA z=uZYCgV_M%QXmJw0w3Q%t|EGn`#M7of`_>H9CCjy%xe(VG+nR)QibSQ2D!#E$Umzg zF1JH&L5DbW5>_tR(md%?$iY=1G6caIWHYSM?t+XukhP9W=U^Rch3t}^lpcV*uL!c@ z|6;~M$Q=Zb-~5G~>KoX)4iO_wU_;0#!0!gZhKIq+^f5O7W_93#;JtNtO{X*4Q(NgU@*(UOO-Gd7%a$t4*WU~)-tn@zqtdhg3)v6Z@^0n zWHVI2hY@%x5g1`DU_lP<4+9jPf_(f6at+pMLFi2+3$SH^l)y8&AcYKgKOgwzG5G6R zu(dO+RI4EZa3Dk32I~hK_}dhs=u7D}Sh-)78bHM$dG7Gsxw)HjUbEq|D`xl4%Figx z-gZ9E9Je~j%I=r#AZL*AoGqMM4UxvAd7i~@ zSs%G3QKpE7G$W?!@#+_ceh(E?1XlFsb$|7*aryT~U?g}hmnYW(T2>Muf`eoXnctbv z`L=g)@7f{Vp@0$WNZr_@u{W@O4>34zaNOS3;i$Er%?FKTnsLDPi?DH6vhgC5e3uQb zyB+Ku{VYkADQ1S|m&~;-dW{d7q-*oEPhcNo`=M^K95C&POTZmbzo%Y^oxui)mWU|$ z-S}-a&;C_4%QZ)~W#Ee%G#gU>ysfrs+|y7^@F9FBnh@7CziXmj80gLS{yW=GQVM}ZT8;Y&VjNdYpGe-%C$1f zRaay=?QxDV!JCB5ubp3L;$q4%+&Pb+ZmMCg5UoIxa;465E9Y!RJw`9Hli8`Q3a!q6 zPE~*U%l+elPj5ZdIZnDv7;5)ye%n-DuT$^Q^1j)u;|(!^I!V!HzGD#jlKcJ)zZ=HO z^vd{ZBx!_bzScaVUabC5ygi`|FB;-llc$8~2H&6dkl$aG-)uwF`Al~$_kRk@^6qe9eB z)I6=(psAp_M{`dxlfm2|8WsS{ifeD>U#pJw-FgA_;MzU?7mA z$l1-e;%kj`4`&LF3&wg1d*Vp=j%Z>#;aJBQ@d&=A^>|%tt##AM#>L%5U0J*>yiFic zp765pn7~P3F+>wbN}MD+A!BM)q$)DC)U+dwD~%&91}q*~DOg+Be6$I+;o8(%t+w_u zDKR-@RAIElsMaXXm}z`&p5pv*-K%=WYAI^dGY+%z1AHEb`GP6r=E3~m^VAsSbY;?; zj&GgIJN4=HJqNlyx)YjPnmX$&>+%RT1V_dXI+J^ydrg!enjYbed>?x?wtsxK9%bI^ zwd`Ha9$*>ulll?{lm>S5t@);t=)?$CfVI?5(l68X(|fElp(B{@XH;mn)PAKi)+NK* z-Uer$WS(fLVfEP7(yqiJ%2GxDw0<)71$M{8sfqoA%Lf1O?(uqrRlJBWZ!X;J;O@l7;r{dS6EprZ&o0e z&@Vq+J>)FoC^M(wsJU+b^Z9FSe%N5Vv%E6{UIyNCy5Q`t^h+sjDt;jj1!uY zo4T`Vk=lUz19efvQo_d0UeccK@vaZgVwQz{;(jA!yb@U9Km4zNZuN85z?!sDG9 zoo4lkbq0jkb_^?nsoGn^rgt6gy4%@D!ca3P{jK|2M=Crk49e$~?I5NTOa^%a<%1sw zz4`@xZ-%xEErdGLG1+6Xzovgq-yh#MehF}{q~WHKZQ^e_Y?W%AXsK)!ZqjdJtb0{= zvqq-I8kI9DVd|OcO={li46Gfl7Bhl*FZ)6EncN?_TuHa&6eo;hLU_@h*QV0u-CfdU z+Vg>Sm+C<6qa3FE?h5SMNN8@q-)hi$yp`0_Li8j?bk>s!>H2hKe(hlH$iWf$iHUKC zY3=EVIrF&_)5+7u5(SCFtnsWF&I^atjMV(8t)hd~I;d5lp{$A5vey2lKBB$@_Xu|! za+@2n&t$t5a9F&85|%8>maUQPk?oe5khwSYbxKvx&G+Uq`W=N#L6i8VIABP5$Ur}rTU6Kifu~j$_>gm6+Mk)4I_i=28l+qMn3u#`k^{y zI$he!brcNK3~lCT&yShkJ-^m)!91MaE4?=LI1Rdj9TqF&B{MiXJ6kI0m$ZzQjE;*3 z#D7KUqFci4LOlN^pV42`Kf|tPi`duMBfN*aaefOwOynp^8`(BuJ%yV}f?9+Z`69!6gO6p4Mm1J;B za3L~RWh`a`X4g(7PbCZ134XIv*xt-`Ms06Q?+B-kQ$yQJqmx#V788EA6A8ftwbt`3 zjGB;|z{)2TIaS=s#QIlt-A(pQtj4&;SG7vD^BT6)i(2ek{uB z&$azx+Y7QhIjEzA_!4h`uOhk;O)1aGZnQttBC->CQCCLiz3$mA9ODjMwl9aXaZqRQ zjBttYm3W8PZ`5YAZ=5_nF-@A@J9lcX66(f#pdNJ%J{TEonJC;%T#1^N`dzSpm6C#T zCD@}}m8B|B^Hs;GEL6!*u2TN1F{a@)@65cj#tJ4s44vjVsXb9MzgOe$lt($Fv^@t;kAXdHMLFSMdU>Cce(*Rg(0M4d!EwhtgWm&JO`ev zuw3vReC56Ppjd6RVq{>fYAk>3!}S#5&~gb@gl%|I>#ug}_HjZof!n;kdAKgI?o2JU=0fw2rcLejZMKA~?cC4zV}3Pq8nVBcNupV43w*PG4ev3Ia<^$OVmeFV;N?tCtl`;dEl&}YzEK;vJAJnt%S zUIG6ef9rtjfb76O9((ZN;BWqR{sNf$MV>zo<>qtoeOvqZ>}+-jeXeJMdYxJiT(FEr zphi&$WTfkQr*Zd={~CNFOcbVwc8Si4*NAWN6ZzK%mk;{#g@e}x zdjy*NlEHUeOKu8RkDJ#Y+^;zBo0rJ7FS|}|Fa?2g; zK~_4)i{r{`=2rEm_21_BbLv^)tT)WX%=5jfy)B$1&cZ$}CynLF`o>tnXrf=G&oFN? zPq3$0hRi~SF8xDK1v!AM)_uF{bZ0l|UYA@~J9#^qL)k^~CbxBeC#n(o?aSI@+MeKV zwEe+X;Q6frydhqxEevnkzOBu$;{Z{!V;^x-M-4HNklkKJ^dcVYY$0`ZTXw&OPjRPN zXH}O`S8{h_7q@#;cP-JGsMa3WCLwGi$aSyodQP3D@W^Y)LF6yp2Ps6dkg=1ogUMmU z&>E<3x(&LYk!8tO7(;Yxb~WoJOOLgPT1@%T>DOsT{6Ls%_iy(jo+A_zm566ZZ5>ZL zM8tNIDXEyGNP6BWCSB<)Cxw!pb;xx_kS>!tI<9tpBQ5TDL0mz4-r>-7rE{9HgEHQe z+Ow?Z1+AH?MJ*v~l0B$dluC*RrHF>1)%Q5|Br zpB6&%qIOW)VEw*~T;82V!DLc)JbT_N7g; z?NK|q&6;?CKx<#vPHp#US8uz3$Fv@9k>K~^uMy-4($4Lj{arh|jJhv%Q96A)GhywJ zMfIcx_0-We(XsUTviE_{A#X{%f- z7w>`(z`t!>PI%Z}OVA+P?U*HYb~tq$Bndhi$kAj1Z8z-*Q--;nJusip6zV1@&YUpz8(kI>MNP@K;N;*kGyHdLT z058~+pLNfW&jC}Ibl)eBcE9Vk>qg0GSpF3qk$98QR(~1c>uKxKNg-z>OI$c zwNIlj3wUT*FP9y`mSZ2_Ea3?IuJu**-RetXYBAZgi!^@M=Pn=8_YMi+D8Yzuy;+AuB^XXx3)g5ZdcR0M%R|z&5`&= zt;YDXt-ko@t$*;g_!D6BXG96Xw&Nu6R5!EBk(x)z1}vFVvdOkx<(=%V<6Rx(Nb+gw z90fzZ&@CpNCk?d+w>z~xz}K``wxl-wX}nn9Sx2druT8H1Qn$bEL#rot#ncwmGwK#JB-T6EC)Ek+J?jnXgX_LDi0f_Yx76)uxLg0Eskw16{(b8NA)Iiw zgF$@UIY%<>e%N(}Dx#S5JfLYZKGDs49@BEc4|dQf)GIxzJqZkLh84Y`htRXVr?_W% zPaR_|V>4r%zQ1Rjwu#K^o+Bla)I0Bz1f)GAZ0B`SJh6}9M0`xRM;L8CN~|N`2pijv zxBb8?wfnYBwA-}HcC02EcO(&=I#WpZU>##Ye&0Pq4W{bS5B7X!STRx-XJG!oGsvU24BX+X@<;i1`OgPk1_!ykgm6#GQ>56orGKM4(*9;#CEmz>NfQ@BmB13SFQAx z?AFef7i~iPCwxV#Qv3Ec95I4G?6mH5gEh|AF4?ZTU1Oc+DGc%wMjqXlS;pAFs$(j% zE-)(qSEl`6`y6>xu0F4v`11AD9_{I)70~VJLyUL^ff>dW(xPcCWLt84XAWr- zDYs(@a8EmMpnS&$q7E^Y@Q|QRNNMZA_qJQLtB{g9nBW(ysRfj9S|>GyPV1=zF8|WM zsb6{EEAPEvui(7MOJp(JHWW8vH=;Y*Ig$hQxul7Zi9M3#67qEYG-;+_rc!c8;yJA{ zy>MD_dT{#3wC1!>@@MkVBx&;3bYe%x?!W@6%5GMB_Q%wxDW}odks`qex3}k1p(wTh5 zWzg?Gsy(%q=19w9$}%4?#~7Kg&J6DU+9f8fCe4!8lN?F6J2rM)A$H*{@LjFSt&En@ z=5MXNEj_K=7FoPWt6rNQo`auhjcV-R51r zWF_(--IIQY$!6%Y_p$<6Png#12P_9pUatrDWIujz-QW;Ep1)kwCCm^w3oZ!dg}tIZ zqIW~4LwknC#NMJZVV=-TxK!|gA0(_3JQPv|r^U)*rs$X`O4ujxh3_8oUHEE(a=s@2 z$zY%$pFbfyE_^p+J0ux-IpR0U8F3wJ9L0|Q8(BFn7~48-G|rj$JHe1-NSx8#P`IjKCa)l`s^F&ZT>hSXqwFVHL+S9`%jrYY zu@ZC1dC5M>PFM%;9JL=^IixvMAPN=n#Baqv1P2Axyq!FwzDy36vy;=*pW6Q$@*Jyy zFFa>%W&bmtH}C3z$G|oLR*)c+6N&|C0*3IWFc<2%$6);sF|0mpFw#4`eZ+O-(&(yD zgR!B}`J=p%ve7f68Ka4#h2wk2Eyj`Y-qG^WxuJ}qwPJ$kme@&rS7a#KFBXfq!cW2$ z;aTB4K|TM8z(Md?uvG9$uu6~$*V&-Ak)iaV^TU3_lCh6t?8z^auCt7pEZA$WfL*DZ zGVwCCvJ_cutQqz+HWgc;Sg2^Kyh?eO+CjC$S{JqQwP&qTf~lUFx5X(7fz<}<0_z}~LpIYkem1{s?CmP-3hcb>6CH*f{yA)MOmj4JGIcJ3 z>TRw|g~wjc@1F4sqzis86fZ3E`rs4o8}Dc6e|>Ryz^%X?OGX0AmvEQ;S{}AicNJwd zHTdtUw$-m!X9gFAmWK65POP(w+7;coekQ6VW_H8(Sn8(0trz2OZ<*Vwx8>+o`>j{v zC%46KpTFbSPS5Rkci8NBu`?#|>#lqI6c0r2ryY2^PvwBd-kbY=?K3*?@{sY7?T6cr zP>yU*@;_RdG;{3qao3a8r;1N+JnMFjo(kzrDjr%bv`T2D(0>0$ML5$r-1 z1G2PdlsWQw8k;)K*u}`|RqR#jf7n;ox2`XYOYFbNHRH+;-WfQ_9~qn$<%+t8uMIyK zcOS2uc{7tGb48{~@w?(>)z7New2HMlbhLF93|1So8Ymflo>xAPZE9|YwT-h~hHufL>jD0O7gk2EG79rsdwbpE_1D(_h^kmuvi{Ec2T_mK7ewVmWkd%=My-op zn;ISv8W`5SmKxq1!HDcxM_Mmf&yRA53W#1A6SIL4B_CrD9T+1YfGzSb$65 zE`O87R|0$kodXq@ykBx{Y2o6_0d9Ws{@Z=#`RaMKc)fSu>~X+x#IeKD*J`g(xly~m zv%v(^(dsqEHGU~qE0bZ*MJPv-Ym)gUV~FS=v2$v3w3+WSC#F_St(nxFd^&b~%xsK1 zDmPp+^h6Xcq72?2oa;N^x45^M{eWS_cuGfmZc}F|zT`XIiJh$w_YaXcoeMe@x>j^D zq5k)vtGZK}5=@?@gi-E8etHD*hRv|r_)hnrThaDX6Dc)h!|wB4Rj?)(by;rcmr*cMD3Z&S6U$=c7GQ!f+_|E;*qQY+ z&!#p{4Uf~uzYbj(+AGo$r3xYi1|pX5nkZ1zBDyL%G~_U}bJ%WJX^c9$VqAW_aO%?3 zs%gXNjM;?QeArj4#yrLBllPa`QLt1P$IasIDitV|LH#LK6K zzBM8tGI`yO^>R^W(GOyqHzjSo6(5oKV;6Cc=f2^CVTWnQ;*YnUNj_JW`u}}PI3`J6 zwfo1Ol-T}F?jft!l=@ZpTe^0-*4ZtzA2-=xdQ>M*~|EjBcDuYXxmvsCj7HRxo6zCN2zg@^%^3^vF`_&^)^=ndo z^tYYgR}{P~N-XUA^{?2!)THD_`QUl-=O ze_?&P{%J|}vyY2D_-2oNi2tOJ{VHd{m#(j$b8GU>eGM*<&(HXA;QQ8JzQy0lic1~; zdi~kmaJl|fdskZ^qwS&uPRwo^W=^GYYrM8Wi;y^2Gj z=h_8N0|S>F5B3UK0o-suE@R8C#233J4ul;NB&8)EJ!zV9A!Xqi^mNwQT`7~NM^8OH zt$U*3vF0p*WkZ_cs2Gt&ARv*&TUwolm$|gd_wB{j}=sYLn%&%QKc-Uz!}SG0@vj-~Wbh z!lDyi3%vJx>3JVsfGjljDE3(CN^#xj%y-sws&>NLgxJ=Yd6*CAn;O_?9oG7#Bvlg2 z$jSbc6iBWNyA40)ujS|Vx%P##{xaLhUgYi$i;i}@w3XOA-L$G8y)*I_OTAX9cYf5oT44cjtiZeoartpu9@yD zJQ^09Ubxp=%jemmU4ABhEI&Jcu|M7S{i1a5FrUvJOFb{RXt_%5uQ@bZt+BQ?zi;kr zbl%9qAkpBBW|8Kc$|BV;g$RW@ILC5!dd>9lvDITiLz80B;FUo$?#uoKtb5G#o?+UN z?(!}z(ncsU$h3WK>1pn1@@=wh3U9pBl+)1YP zST$v1We&u3OPydHis780p=qLNt(A%OPkSwgKW4y-rtkMUiw()!8YkC>%6CGW|bXa@uD7{f)_g#{o@@3iQ z>mQvz9DS?vhW>oPv$02E4@d8Z-RroI-VM3;^^W?3J@oRFcdd>ZR(q{8!Jwkl@dtxtPQTv{@S^QG`!nU3G{toR9J?b*b zxn4T^ChR$WnFyOW1LxWOW%XnS6z~e0U{798eTjOE_Ev3ggBXL`#(Bmh3k%Du_QUq~ zoRwW}F7R4-Z_yIJ4a;noCxosGtBC#*vv%8|gt@)$y8gOWs*6>7<*Vd#X3x$V zPt1&K4_6H34=x=%!5!!?<`{CG^vytB%c1uvYlN9j=l5)%$~kE*-aaJzn>L7~C3Ikst2^W!E`3(}I@a;rJ7MbJ!c zp5Hvy;M%acaeqTf!;$*E&DWYvx8}4QBsLJHx?{Qv=_+)SzIz<$;OW8bBU8h+lcb41 z)2Y*m6OI!{#M2Vfo?95a$gslSsV0pS+%`qUsBi3X^zeg0r?qo`=BYgzG5> zoS>J;mA zC{HO5!kM?76P^?9B9btZZ_i)KZy&S}7m1$2+P-k&)2$K+L5{el+UbBc7Ei82liXbiNErjPEOSum3;drr0h_XJm^+N9d3 zF{z=VeLJ!JluOFL(}gK3&orF-oBDrBOFZRu`s*&o zJ&EfdMI8w^5V*&dVLN58%J7F;kD5K!8S6dcHxoIiHTamKPFdTuqwz}ly)w1j?l1Gx z-@a13AADDGWB&Ets}@%h)Apt9O*@!&Anj<{;k4wmq_mT1C(=%*rKF{%olQ$iJD+wr zZTt0GS1cyQ~L%}c$^?-_?a&;O)U)b(Sal3$V4^rKOOxQg%_YV@ai`f0JfBz7Xy zvPz)NM&n=SUlcDDhfU9)wwKkHJ*Iq2`GH!#8dq~d^Mc+>y&glXwHr@v zy%V3kL$EV*@6f)P!>5jxA9p|b>TKx$)?pWSgv{q&P0`{cR9@$m9 z#X9~|G$lqDq8us=8S}zPO+v-}Z;> zckLgvg4!R~z8e?o{Ji-4?5~K5^W~w{m#YHmHr4ty?QcAdFKpF=x?WmWOXnEnGDW55 z4(%(`9dpM{K;KH|uwq$K z_IY*_JC^OqE@7o|JUJ`+NBaKolll9hezs@CY2@+b_Q^%F*0Z~0B4sAzN%ES?3zZAi zN7WbUU)6tZj5E1nIc;fT_u1~7<2t7w?x#JFg+CWs_>g@ne2)5NEjr=)^O*>}~V z@r5L>VV6ADh4y&+7V|?EfkyX@NV-0HcQnd14#3H*3Ty{ZUTM|XP=PPWH2@Xug=Imd3gs$gFDpM)s8zdHo%^%7`r*I#f8A=<|C?WLUpKGOuR+ja)3UGSZF6#S z7Gyc+8+Jln{aam6?exFtzq|ik{5$=3!Qbe*OSP98it6K<&NUuxKHntU?BDbQ&JT_> zIyUZW$gW>nd#pyS&bD^C&aJMrVW9p}b9Pe`eyX(&_G6Y%zLQ;8Uzi%)SN&_mc+t~| zhzaKG?^$iR8Cfr-2qitOAgwzFjRs#$ab_yk$=0b({Z7puhdo_a z_5|-L-hE})&~BB)y}N$ylq5Rt__DJy(PMYep5J>L_N_d?+MjUn?LpVW6GyHkeNT!` z-kfZIVr{a)iRzQbQ|wa&r&pZOI~RNIzY5O(d;169SEtsbmZ$znErA+OF|6MTQ-7uA zrxvAtP5qwwCG}fsc4|&)X6mQZf~51wb6YONm#mo%{pOwLlWJ{e6Q@hiWvDo)T4RH; zkun=(vf<1SP4GvcOUtG%sef2^x;X8p#)sid_a{w{RIZ=D>YMhzYm4BHfV9OIm!{pk zS$zHO6XB!FZ!FVWK3&XyR&X=Fv8<}J6XNwzVg{j;vV`)ZXF-oC>l^cWFRmBId(EvK zOdO<$&x;MF1||tIzh#c9r>QF$EHtn-d1EqR8UtCxsM$l~5#w}2reTYYqi&BnRzptJ zPqkLLSJ_Q%wb}>uLG?dy*n3ddUhl*_>b$F_sHuh3ODjjamv#mYR~>#iuXPc-UUySo z@NWUb>#_HxMS*_!#XkbPgRGX#3!V%%4bxkz6k!q>6n;1&XKi12c<70+n9wa@GNC46 zwpM|_$vn}*?*uyn1LY;%{Lll-+E?wp2;BDzdatbx0nZ;?hYp;}#lFyek zN{U7AMB@Gz{cQR{`T;Vz`v*~h*o-&9|7|vIj;lReQ(M_vG51sBC!yebJ};;0i}R|1CKziJ5ho4W09zE1S7HGc`RuT{M|Gc>wZ@hT$i}Hp5**YQrr< zmq(V3jEw&pe*)+4CTGWHJK)T@x!gjz4+=F3ZHhca1LbAP7uCM1xoT|FC<8l0>1OHD z^#b(w8x|P`8s0RlF*KW}ZV+MMtiMUWM0-j*SA(IEr^;1*sl-=u#0BFpa#*?Nm@63K zT=m?~>4IsU@sTl!h$@=w|InYr)McI^v$|c|FSlvJ8oaX-Q;92eExBHFtMK*rGX>xB zPJZplt;`9}4b5@SMZdJ>zR0Z~RUCKr5~-8efdp#m~bZZT-_K+xoJ(w+U_WZ}Dt7*PPl~(y|Z#ruA9d3_gKa zPLROfgc~^n&NYm8cT)YS>h!dpLo6Y4HSF_$;skMo+|yiRe(qrEklRq>SkBnz>G#ue z$RxT~VW&d13R@*zs{{B+&d|i@nvtIQGjo>ZW~)E8?snmh^^P6RYg~feaUMq&=q_~f zW%&;K<@j4I5iFS>gjp83+w_3uQmzR4$)h)DA+cnJ*0Zg&Cm;BZs9f&+;D89 zcBI2PoAt}1*y{|Vilcg?lhzkUg+!f+9*OddL1WA|Zi#)f3A;HUu4Id4{F?aR+q8FH zOFX@cwYzoi-u>GT@(w8`-94soqVS~TRMzRJ)W-j1tA{H!@TpF%ORa#2UkYo)U%*_2 zz+45XKT~%e_Br|@-Xr1u+M@8)i#GcX`DRr5{rPV-M&lopt_I4vk`Y1+!P*qfs3 zT@SY1?|QNS`M~>{jP?0$-%=}ED&EwO)rG_E(RVoeP~Gj*{eroWY06XORR}YME5^LX z&y7^EoSi=amicu$C5Yo#JFv=SQo!OcJ=*Wwtvs!P*+bky47jxEJNja57cn#E|5bM zPJEfj7JU@SL;mO6JIiKJQYbRSmjp?}@`jxd*OwQc{TZJV_C@{u(7O{aEnYahXnV$c z`sm4(r{+(Zo+dwe^4$2D$7?wL^yXW7WoFX*l8?tf=;hIKZHwFr3(Lk!TmHEHSqwgu z-74Q&-QnMXg_`d$>pW}YfWm+Q>;)Vh-Z{JsYW0DWT9c8IAjybiKr%TeBej)Lllg|F zVMi3bl#-MmEB{nxE88fqQTBqfbrLx>c}L6^%+RdP+}oLNGxsHLCA=vnL=5I6Iz^q@ zJiBD}t@N8TP-c_N6WMCnj|wb>IP7OE2WyD?j+MZvvJq^ZqOMY#GNN)qy-VFu^m7P?AaA$DpvhJ{_ zwpywq@t15HjvcNUEE&AdN@86j$C2Y<5BzttOY{1grhnd5NtLB#qouRI*Z=-pT=R3} zyV>_A`Ay$^zplz%p0ngj#3#$__aEZ3@-uVa7iO$`clW(-hH@q^WBW(T4`)Azd{WFO z=V5+M7w#@iEpe=huFU;It0vZ})M_@Yt2b>q((K)K6~6`QJ}0_y-OaQ#+AgLObCOle z;m|ljhBKp{ag$+AFX&m@L#I8X&7;-R zchGP3MD$Q;*Jx#wO%zMuDWm|?{maAc+r=|&jWrg`L@(}x#f!ZRrgm<2b--q8}@1K<1p^p^zhaQvxuNb z?s}i-u$XHbtYa^2+8^7xDP>dB=K47At+Xxf@x}4R2|@8Mx4lc4+b#=xXNwb?cG>T0 z*!^|)!o6?yF5EBKmwjO5(ETH!NmIv;p4^nul#+fX^IS{n|Fbp1H+88^ssB>zft~)# ztEz#WDj=^aP5l9E6@O;Uxkm@14tH-$+ddMtGiJ$((<|9NG+(~+Uze5E5^MeWx$||@ z9;;=+yP^Fe8Szv4Hu~Ep!zNLwcj=bA#IJSl-n`}Ae|FC!?SI!6q!bj0%JK} zIdjSE&YN3y&#yjxm^Cl+Y{82BhEie4{_3w)@(pqId)iyu%4vbL5zZ6NoUmTFcFJ|i zQO-*)SmUwACjA%shNcfp{cUP&-nlGy?Q!RND7p=~ZFE}T9AU5PU}R6RUutb_L$J7S zdBWVmf@yW#+R?VpHpteBuE+M4-6w~gjuiV}4$tg2In>%_+Qr+s+uPZ(>`H9T z+b**iv}v@tWBb-=r}IzO<4Xg$%(?1(o?`Mav7L zORoQxRyb8m*Cf`+L*2-Yl-1!$Z|My_Ktp&_PY%v) z-hk#gw`p!Yyh~XRZ&zM{cky?^r$xeIc|a{pw^ zWp~57ctx}2vnQsmO(l-r8f6PQ1s-q?@(44OIZeAk>+ihQc?nMJd~Q)~(P-FR-%z7d z(^DBxsZthOs#U!5XYG&H@BZKH^KRr^`_k~S{sT2L|NVwc*Z0rfM`q+^o_jCKD$Shw z)RA42^XZE|?8BVN-}^22hvj#>V%wk9zkU?ID{CoLsf?~z{CDr4$(o=VYpB!JHaj%4 z@E-WR9X#UAP6^48a+Taq38lnA?fV5?iQWbKyjz&2OflmHBaAhl)z5m&s_VVbd$>2S z_bA7KbD-C$w~&3By_LOyeVw(QwUo_f;o;2LU~gV;8RrTond`)j7*rjk2s#Ak#3|yZ zuqQV(N*kS;5KpX{*)-!gr#(l3cce?@X61G(UQ^rv?`v>0yfl3vMmNn{JKx&kr3KS^ zzfGV0I|rOgg^P(N)05!U?oIId;G60HV)4I#+CZb_Ml0^D%3l3*4Lkw^*<=zox+y$P8h31~U`uGce*A}RPVvvSVYlr_c$c8Lqj9_6&Z~)LyRCOU z+U>EYbnmG>6MJ9p*Epzg@XMj?u$^&+4Z>Iuv=tZhP^L3sKDI$yNHn%ltzYzxGUCu-Jv;GGTYd zUe_w!>Y(AEp|Y~4^0CR6lREvQeT|)0I(uuAYWMuiC@RRx$TWLi@-*$X?5)*TDlfUD z{m;!k%{^@aG;e5r&;p<>g|-~pYG`ZH9By8`KKv-*p+fqlSMr}aKWY?c<{$fg?bmFj zSEZzWQ~mFDT>Dm96V-ty;?hU+N7H38WWKAB)r#gJ^Iu!2TE;pkIX-q??efyu)}_@k z&FQQiYWKiy%I*i$=Zo!=9n2kC9560#q0;K+CgWb@KIB&8e%NJ`Ypj#9^XdPKu=fsX zy6fJ4rT1P!4=vQtdkMXRH0fOh!G>7S8$@mq8+Jvpfr?V3_uhN&9YPO;l8_J*0)fE0 zpLgb*IcLs$p5Lr-)IUTfTG#ct6!nyJ6vzttvh%WylEG3Ku>^4zv0GwXz(X`D z>LunOJ}oW{ZGg5*d&$Jhvch2UDEY_oC-M~vVTz*)FBEy;b8xikpc+L3soAZQtGi`T zWtd|89^qu9Z>(%6Z&a?Et(U3wUVBibN!3P?M=4O29i}GPC%GtgS-eTeQh0{nQNV+1 zi0dyK1A82O7rp0k>~Y&}?e6h<&ic_@=3M5m>~LKdcb9)-enWKikE$1?9VLW9+CrH; z)m#YZ1In`AWFr2^{dW2dOAq^H_LDt*`KNcf-Y>I1XTUR7_9rDfGE1z0A-}eyu=q20 zuHcR58d5rD+avlWdhJGD4Hr(zPx=F$W$-H9s`Msx<1hXN{>T2u{R!#{wF-D=NKC5C z`>f7v6713J(j1{2Ev!6j+N@VtCz#n;;+VfN_c5(7Su@>d(q;B!KErIp?9B3*C7Y>^ ziNc`77)_f>8w3~{BS(WrTx2D(5J`fhMj-8u?N;sH!QaM*?}qIz@22l&?&jr!fF6}JLj%YP{Wl(`eq$yxdc`W+T+Rtc_9uCD?} zf%o9K?uL3u%E^w(#wf`tOR2A_JLuBtnHY;8ILyS&+pRln4D4MU!p^>SR&w=pOZJ%e zyyH{h%j@Uu{~)j-$S1fxWc9rD#kKQB7uhcOUerEcb74Nj_JU1F*?H68vXGSElMuI{ zuHZ`{$n!MkA6!_!cqQ!Q())IVJI!W(usxo+0qYQOdIj_yAj zF|qLz|HrC~&b!wW790NB_sH+9%Z96lt+}0^xtRr{F%KfbFv#eAhCoYal?aqmo?6+LlEa7l1Ya8Edw;04MTR6s&t zLU2Mz!bMP*KwSot^J`#o9tG+)sJozI5`MniNn-u{_><9ZZ|C`>iTM%>C9-= zXp?nY_1eKcxYzg-qRXt`Jlmqi(#86!&5^l?C5tK6^rzulBW-;JgU{OcbmmkVR1-jt zg_C?GH7V{PF)LysiWIpZ`bdl@<|Mf&StVm3TO$XTzoDR@xG8sEJ_@EPrz0~a69}V| z6Oo^k*HGS49#(s#o~Y%a{Y*DVPYLwXVS4xU?`oUsU^H4aAHv;Kek!>u-;h(3ACkHw ztu9e1AuaMvuey!ndu@-%^ZIYCq;B8-EZ;E_?s>J?p#f4~FmA zli$7nlcM-3F7@bJ;h*!r)eDGuG?i23ht2%Wti6z)RNyPP2Q}`!XZ4Ua)!DIM^Fm2<)!Rj?9p3G%Q4hDua+6 zmUWP+kja&im6ekIF6|>ZEZHe$AwCPS6I$g(@VT--XK!W_VwMLQoyXK7lPT)r?03Ijp?TM{D>&YMf%_Nt@6w8 zCHtxQ{P3Ojk@m~;q4m@BedAZ)i}c&|mk$gI>I?o7%zqwxKJ?;apqbIUI25WCS{lZG z>3+CVctZp=BJhg)_1Z|LsJ@#!cQWq2j?uh76aW9N+Ye^qYwokFGI+p`uIEpyHDEtbuu&7}-!jP7dKX{Jh9NoR0FdH$iMQ0umnw@r`>bKPJr@UXF? zA-pKI&?nO|Bl)}LH@@Vm_W>`ao^vLKfio%soKpWeqi%pR>J~VoqW||96$3VV55Q*c zVZx(?-N%qbiMMZGXMD5%n)dr{dRtymE>qcFNkiRmt!|rj>p^c=Z}imiWHZVO0pwqyVP0uI2wNiCOPWQRFw%XJ{I<|G!(!=z&cy2YO5e@C+>V`g_13r+sm9<2 z+1i+z_A*RqX>oQDF@GvgFchlk^fD5seZ&60G9l=6=h1$ZE?_%J7pGMJow(Ajv1~Cotgg6FZhYhJyRx z1Z9r0dANG`jGRVxA*+(V5Tl46_HOO{*v;I12z2}vyKTFV2p0+NBt6m?X_5p7Oc8%_ z0NDq423``s5l!~^_b%=l?`8mPH47TK1=Cc`F|hRgMGb!Th0YnW;>tI{ezR~A;Vm!B!SUKUpx z3iNmaMau=A1G(r2>B8D2B4JF3*4Ss+zd*i6)<4iYs3sSY&yws&U-!!QIR2x}$MRzDY{qYa|H$@qe?k7J+1p6?moI}xPFOG#m=1z4!uw$fW=FEx5~idLz1r|w%lFC!D< zk0w7%Z(H56eqi^-UgE5a^RAoFIXZ7M9}~Za{t$n!fa?KufocJXf#-r6zzkO^I6SyN zL?Ptm`O))`i`5sHLrLcYE?&D(dhz-BGZ$?yj)#g}d>g76R&{AS%rN|WIP^01^3$th zSEH`SL_(u9qIPdi-1&O9ASU5{Og!@cSpBihaRIk{?+9F(z4|Fs?@~3;4(@rHdK2BW z&p}BQ;2(@WOBWpLZ%f@!`*ndJrR%QhnR?hYg;xU;qEZ#P$$ zFRr1^nJ#or0cQgo3!IqkOzbaP)>vIN{c46VG&lO9Q>=SaHBik;iCOs@&<;G7vyj)3 zqm%z3XD+WHKP7K2N0ti#-Ij>#s;q%bhfJunlng|6S{5dED7T}~tk|af27Vc?qjE#l zUu{#RPE}29S`DdEs>-T@QE5<4g;ywDRAy41R=%pBq!=ZyqQC^}hNa6`%5FjvCEZ2( zMPPh!e3w~~tm3qnX)lqlkb`k&af563*YX!s7M#Gn=jrg_(D~j!J%wQQyVNMw$WWtH z-B#XF771ouHF=+Mg>qQ_)@Ho>qX%a6CO`MnZvBG&983TB>ol_^<4T@Gu2+e8F<*^j z^;i?Su?6UHMuD&F&cyUMKQeJn57oJP84JZq14e@_pdh_r{>d!M^AArP1PgJNXpvZz z4w2zg_^40;7&?Y(M7391VcM%Yb-JepwT39eYNI2g7GrfIdgIFmu7(|YFZHdpM6~_X z*wp_h1}m}4<;kT=>Pz*A-V{@TWI=-X8u`fFPCRMshwQn)U;mh~m2rVyfZ-Bt9Bl?* zGQK{3b1Zm(C#&r9>`xP#2`9UZgxB~Bc(dJ8eEV+RZWkeo@E+VJ{q~Ia*od4&7(p92 zr+Emk@QI+j@ddbYu-P93YHPinn(gn~zFQ(&;p=Mao+w(B)zb8$0r0L-XEJ7V0G|R5 zbYIVhn1^5k#r-iC2nCOJz!N$-R|JP|{QWSItG;MVnhk zOrNNqjksqrVwP=(U&WkG8sWd!iB$UK3AW#vbVk(LJjiFCC=8P9ns!IDPjvvFO4{t z2A7yfqDYaf4veV$5nirwQ`1W4NXOR@JY9%xgubbY*&XxC7MHA?tY6rCw=J-lw)MA* zvp;Wt*CE$&(W%hB$wA51&Mwii)Cys0Vn+q z8-Q6qy<)y1Md_pRBPD6&UrHv*mlUyzx{58}OgojYgBi;Kl?ALvN)wPXE0|w;|^mVU~5N`d`T~{=dlyu{V4|aPFFym#xwM1kfBGbJE=XZZLk^G7wcH- z9#y@pbS~8?X)nqww8`VmJw@(uJHqMITGa5GXBOxG0(v>?}*-6|AR)L|3%55PJjpd-W+DOf7*6h zcd~rq`N;C{<$;oZ3E&A90J^BP7VVbqMvlgT+S8ivRe6;*70TtflD*C0KTbTle}q3+IN&`Xk<<5Y?Pu&K?`sob#D?9EyY4`DtBECGpxel;i)b-4 zajj_0Z>4|v`tsUR7vLE{7M{+tBMaw@=DKDR=lFqJ;O^`laL3XCe*MFxr%R_RKUWym zU#@Wg)&mWuV!Hu&(NX|Q&+9<=;MY;|Q4h@@8Y4zuMoU%?)bi}eEwk;GZHQfjJ<@K#UdU0>smGzuQQA?}Y1i?@$;KJsQs=hj{>N>^-TK^B zk8Y2BPeb2}e%t;AftVoOkcIPx7q5l&Um{-(x~>qFc~k99Tl7$L^gUwi&p1^4e=n~G zsCb6x?z>knvt4!a?+ZwEnQ(7ie_C{N$dBVnnsLK5^w!7peW5 z`J2?4A2W}7u{}bymud#Hnlb~vLcc`5+kf*ciRbzG1n~q(P_hX!3GxZxBLI{ND2)Vl zum||hhEfk~C=LIk_yT(Xvjp$w-=1cCWJ~r)JCk}YTRbbS)Uu?vF|XlEZ+DN{c+!~V zQv2c><^|>*)q^^~I>%}QNrBWzNlG`ty;PF4J$3%-Me0`?ela=)dWCiK4U2y*k}NH( z?5zc?6>aux=4_?yr5xb)(hg1z-j4S6PaK-SYrJEHwPrI%nd1#djhwU;wYQa%;TL5m zWZyzLC2K|0L`Q{TBJYK}g`bKZh^C9NiU$Iw+W-_HDF>~Dwt#!uiCDDwQz)HeuEdc< zj>MrvE7VN#L^@t3T~1aWrm(1xujr;Ep){o=q_V6s1%Ir)pI>Y;Xj99kWOOy8Kgv9LLRZ%u4X4!eO#BfTey zo(P}hF#cq`!u5=+Sa4bJ2LQNEzPJR?*Q{J5(D`4uhx2Nyz^I_xyPABk@zw z8)A{dv%&%pJ;;5&e7;pKdG0cxTjFOnW}c<*r_Te-$oZ4zlhNbBV|oDU{Q`8ky+>(B z>{KS|GBus5d?Il2;poGW+!5!|nWK41{_%I;zTt&9n=i84aI1-F; z{5VO#Rk^<T8WvLp8o{+5LTnLBa#C*`cJyUyI*yzw9mFOw)!3bW!%uNLLj|E_(& z>SBrDtcLBMkg9<1{_#=7Q4{b7W}TFsNS(5to;$HU(E{A*b-;1&1SH}5ll~K3kfE@8 zLOf9hy51woK1H6aO|~Ghl1}ma_)*}9KH2=ase@)fpI_Hpw_Y7t$z6sodo3)_@6KNX zED{!EI}(QUMsCcd%`?s;mcA`AFTYvx0h;bxs|PFY>jP`TfT0kM5x@xJyK%5R);-)o z?!lSkwWD#s<@TlbpufW6&vKUIJO`P_lJ`7h5JD9-6KjytkPZYhmuvEK@^#?-Kd2l6 zcT(9>`KYR^=Al8-hzFjT4edgmcpVMhS-n*KGls*!4W(*qju1ByHNlzens-|WS`J#i zv5K~?vCOyn4mA9)tzKJS27iyWd2dU$J+%{cGC0fNEbem2Ey;ZloP)x?=l!&U9tJC4 zd>%?45q%kPrR3_;jg*@=?ogxEV$t`X#p%ay{6A_9oQEs%%khix3-NQHrsJo;o?$$G z6zoL9W7XnLuKd2H5&R=$(_7d#)1%E3=4{{+ZJlVNV8WCkQ2oC5R?K6C}V% zDV;F!oaR~9d%<^6-?3kGytd{K-#W2m8=FKp(|fd{C%Y zX;9%-f4)w$S)!?_tEZEHz@gu3ymV}R#&(8j>E+^IvV_@fsKJ* zlpif|7AgSrg<-0vs=u^@bSU~o23d$3CeEgUX16TtEUPR&SyouTv-xV1U`w(=+tLGX z_G2r5Yhz1ED{)hjDZ4SsxJ>t<9!2Y?c7PhEdXI7>d`Uh=ffdXb;oC{Y$=lOMr*kI@Cz+>dfWv+Dv=8u!`)MX=q5-@35hae2 z2lO2$fZp*C=ZVw6;I?&9m8-Xx_7>e2yB3^~sJSS>M~&+p?8)!k1lxj~wxeeG=9|^e ztFDxJmR<$A4Q9{-4*pHbX3LoU&6t_~$03L3FH=E(9wJBNuh(BOurH0yd0k{!$X<*t zuCuTov zA48ds`JcBAj8_O_@0PutHa;kCiaVc{R@*MEU@LcEl!QIY%o8u7&o?V0^ zn(Z}PCCda$3&R@2Gnz*-5eUa2awKHW>~X zXCT^4OU*8u`J1PhM_BZlr&!2X9avdg!)=yr`|LRExg8`Osg73Qe6#_#=_-#-&u;G# zpLKtRKuEB5h67}C+d)@by`-|3}oOFSL!n}`_K^529&b3|wpWi`%6qUTT zjSAQ!5h`htX#6>JIh=*B3Cqbu%4{op!(-Hk)GuhyYp3eT>GL8w5c!r-R(?R~OmG}^ zl66*ak#W;=w{rJ6x8pA1(d|BY&cIE_{fw)a+aHI1#{#Pg>-R?g7_VrgX{G`VoP}(v z?1q$@^k-=r85y}^x!-bp^3n1{`PV?xr!DlR=$0{ZGM=*GXE~W z3;0kVLDYoQg`$LGm$$)8Z_?m?!-3JHLKLvSNoMImkJg1@?aPNJVnuVjGyj=Ze$yt1kKth%IDzgDQOzTR0wMx)0F zdy_hF|AAPtTS0A9ZR4z8*`!)0*}S$4up*fam=jDoOy`VV8{abUGd!bRrhQK1jAph9 zLe)ZPQpp(HvGOIi!A_?_%mMNiQp?N5_n0k<&63%d`2qb$dUZNBx)YFv;Ct!-l7V2S z!5}Y%_k>7wI@UW@JGMOjNJ*vW9H||-9x5Cjl6T2($+6_yWNWheq1NHG!=S_cqxK_- z6V8*@ATy$m_BX9FtqbiqO$*IykbF~kl5xUD**|;*{>4*b5K(9Elpu~f*x}h2MVBvy zEUnFW%y><9OmL6h8QB~(9_;BA?)}x<+SAZQ?KEk9*mA4xTJ3s8T{(9tviMBFBybuq z<@jc?W#(piWIoQ+&w&2vN|#Ts`nj2Y=hss9m8_P6d->UAS4$C+PUA!4 z%XoqDBhzoDSmxKv-r!du%P7kyz(p2d(`8#?GWAj=>~!%TAz zd|tezKBi^^T@vPS>TsBpM-tqdA^Zz?iBxO@mb_W9sfZp}FIm-Hl~{SbJh)`H#JG?% zFANxN7PC1sEAs*KbBiI1{;ORp4^Z@|G_(r()~3W}?pE}cW`tL&>ap_PF9c=^%Cf! zo}*wWwh8}n**1q(_OgQ#|15^g#kAPez7+pY4?b>usC+N=F8Lkt4dP{DQeI+X0{4F@ z{ijin@4RYx!TTvDnJ+E%`_pXQEJQJF5nUa+X0?;9Q*1nNOlA4!5*Oyf_R9m)11G=~ z|IGE9iv^+sVG_3&Kb7{AsZ}siysNgW=BhKNvuRw65VyoxZriswymg8`i#v;T=5~d- zJ#c&HzUj{I@yRvS?W)TKSF{7xvB|2zy2|LD@v6oRO@8nWw*dwvArs% zQofqLs;#!U#7ONAhfl5ls!HQu%iff8oY9G|XwCJ@H^|cLHjr)w{OioOE z%nZ#X%+$?`%~H)jnqD)r1ApE|%$TeiHW}U5H#Wd(=jf1uCxjo~2q!9JC<@Ew$d*fr zO6iJbiQW|G6lmjN<%My0a-6b=u_iOrF;voZ(^Y~D3p~ggi3NRTI`FdJIS4%HBi$ug zk!VQ#gwb8MT?!zT{l>B2n{b@?C0q+W3Lj4}C!8UU>~)jhk-GuQxsdXLqDo<NqDlY^yf{M`sc5m!+wH&-N=5*P0y zNpm?991}w$yd#lA>4PPGv%P8^5$!CEf(^6v{dMQ-ylX$!e5m#)GboKN7AzVrd=IoR zwR!Zp$$t^qEtxyHw{y-E{aa96`nV*v#J+gHq^;PwQlw(KE))DboEst5|; zdfwSFd@%HKta!A2;%K~ghJA*#;;~|fS>6`KweNf(co6bQ&q&9B8*+S#J*9$_5?2~E z8f|)BdML9Na}i4&OEhaF>j#!tmIAgMwj#D#Hg;Aj3mYRdBMm)?Zk(=$?mkT@jXKZ` z`;g_xs-$J&T>>v*3;z?}xgD_Gk9I=at#zQ3Rx?-JmPeMHk==9Ornje50mG(g0y42U zVKBi8^f(RRv-{1&+_=Pa+>|Pq*Lcq4Pyd-YnNCM8&y6fvEZVI0tO%~+;*B zTXj4BJ1Kw>QcM0r4xu_wFVQ*CA(;o62ifM?9&tS2IN@gIxy|>J@3K&Z5LLuXbV5Q4 z8YYdBHdDw@s8RL;iu`qz8(<&TseVc0iH?$Pk{(Ro*9d03Zd7XAi}+|VY?5ngV=8av zWTt3-&RpAK(X!I&s+Fa+tR0g*+@8aM-;vV^<=Ew9;vDEgb)^9mtWl3bpLX9;|J4A+ z5a08?7nnk~FKI-0U#`CLSb(bTtMW^ zjnRt=VJQIxfnU9HymQ^E-FcnZ&aQ$vDT^K1uE`?ZlA>LwV+k9E$wQt&npwA4g{Wt! z>)ZL;*XMR;!}=t8k*2!9g?T?RdWa8qzlL8>ULbgSG39wUdIdLnXvU3U%-I2T$x?Y zo%Nor9@iL89@yTByR)V`@9R?=5SRVGwit9V%c1pKa0saJukT&(z7 z{krO~nphQ4`=Exd4qCg|FaoxiacsUdBl69B0l$w{?lth%emF$)56bF`>l?DTD7Onzau3jr%li4)f z=+%C^&A9t^mrwuGKF5)d!xtudCd#Hwr;CyHNYSNdAou3sTEv$078|w`BZ(iy4T1T# z} z!(LMpso9i|6hF|fgb-e>(jT3Yt z`1v3)e=yRx>7BH_$9oa`X8V1VT8hB2&@mlIZ1tnNL^sbs%gDqk!iwh54p1#8CCH!b>=yMeM(7cD*3V@s@4$#$D$a;rr#_=3We{090-~kh#Za0bl>0_?_t5 zd(g|hSE7REgMEE6d$iD??Co}cGFJ$}FU*7aq1 zQtZ>|$6&4T|665`SQ2Pn>%BDj{O!|jnoFuR_*w_~-}1t1&(=hC-sz|tSs(s6b2MGP z#<|9~TaAwc^w@NUFAN4?{_HN`E08ZXE@ldAf`u!uDqCx#w402=jXzmfSeDsl**Q8| zJ3VnpaV~Y;Y$;Zh%&S%Dp&gX}F)j9pMzRvbGcDA;r2(tzK3WFEgCOU=M20G!o z6y39iTt?9b3Wf{7?_Z&QRbyWH7JL@=K&}8PAgL^pAW|oAEbv8OQUJ}X!pF{Yizkg^ zj>Cl`jDwDgi%XU_gtq~BLO(->AkHG0A{-)DMQDV5gh%+91b*?6_zZayd4qXA@caZm z_7B`W+;+V0c;|pFafVl)kBQ5XtA~A$eGTw3IhYxknd!Ocbx)zEl}DLJ9pqwiD$pJ; z!s=t4Mz0<&sF!d;othC#?%HGpasrs8)^N}ozulMY&z6337u43 zDqhq8**Xckw7Z*!e-DE|9$+v-41*Q36LTbI0w*`0KA(y3J>e+vS#dqUOVCm7R1N_5 zbS}M3z59laM&YJvW@;8h3pQ(#b)>Z;xZg+EY+DUmJA&;T0qp6f5Je_o#@2{>gET`s zeTV_K&ZbU<`eO}MFeCn;UV~YB8N81ZI)2_ z2lN88qO>L;V>SIK?C1^o0r_Na0-Tg%gcZ5*iGb~S8E|G#fv)i$$dI51d|dB6 z_}<~($7yWrt(O)pq@s3(ZBq%QM~B+_2do&_HNlYDlb2s9CAosa0+es?Tc5YFui@ zHYKzsw><4C?o{YE0%z%~;R|EAqn=Z*CZThWX6F~f7Ry#_S8UdDP#Rmsn`+p0j5=X$ z_dJoZMKepNtud zBMb)&_rNYg2zaM|fxL3$anteB!|=l?@Lm%mVTnt-S-VX5UpOS@B_;qce9vv-H)7FT z=`w)eK$&M>wS7lB(R#1RhmhxfY=zaPG!I#PR2znro%?9$_ydzt4r z8aSGG+IX7y+xQ#7uJBmYL2L`E3Y=OmrOwHImSvZ_F4wOBSHvsF!^>3|RDY{mXryb3 zX^rdL(0!`Mp`T;mZm3{1Xq1AmHMt5{TBhbS7Ri?XSUm#$YKm>L9lia8eW0VdQ?FCj zS$P+TYoL3`IWtdfFIHbYKV!cLf4(5a;J)(*7k-7$Mx4F+`P#k6+8ctmGw!IyFHQ$?2Mc;%@L2-)<4Ct~_gL2q zH!p{O9Se;zj02Tf;BsP_Vs6|&xRXGp&L5l)u4Bo62|aE!ZVm1znUxh4p1Hy~_P=+2 zC8Z1e5=e{re(&?hr;)dMZ=7EKO!9p8@(E+Yf3JxohUfVoPTq;8e*ET_zVnkN?{$u5 zIjZzovsY7g-k2Z?qIY+WUyTGD^6ch@30&`XwQ^DzG z={1_(GqbW4wVQP4cHDEJxMsPxpVRX;@_FuC;iv5%6OiCv7*Ov+^d0b6^Stkz<01?w z63T!}TVnhKLD8=>Ffc?L<{RHaq!`B{VhnAKo@ra@G^)9&>njW@#7S>RJBhv#4T0oB z!ug-_&-3DVqrr@`l&glzmD`J(gUg&tkH;0{X8prkBv36-3ps^k2~mV-AYKp)epmi6 zJ`CR{-Xh+&yxF{M+)dmeAR}s@kB5IzfI)CfPzd4x`r1`KR{mu0TI+FM;|ylG!g7c4 zHe)l*3C*RGppzKN1&Z+gF)5T_N=U?5VCvC+Xzr!Qizk3_pgNH{P8t>%#`m%Jg>=8{ zGU>S39@p}{xvqJ(Nw|@vp{HTFKE1BGmZt8sMz@Bq+Ox)|`h87m^+C;6wPu}Btu4sj z`qlcj#i`4_Q*g+7@CcxQg65IPUu&vs(49Uk18IwBL7|~gPqt1H>8{f`u-dR{aQSn| z3wjEwh+h|f16zj~!dKxF8gZHs{UQCkpfBaK-~`=*vULv7cPZKO+WljfZvWjj*KWaj z(8k%y)wl>}8SOe%XEk-@B4r9JM6OM3ZFce8<#b!Dr-JNFUS^rM$2(>LM2dIC@=Tj_W6mEdzC;dPTlF-fdW>o zE|!8R*}1W!y@SVo#OdJNb_9U4C3^tu_dM@ zrP&F*&$XJ*HF-4oH|8`~Ha%~tZtiYBZG(0pIsw9|2iGszA2#%15H}Vu_I#2up*0&n zBZRy?$FuZikqPyBwH0V3leVGTh1g5jT9ALmPFg4K9?l;+gG@3BkPK=>XG~|paF!v8 z`Cn!#voK2&>j*0+ix!I+3xXw>={ZviLpOsVy*|A&?QPm&ntZU!dwMcO-K5?-_B6-TZyzLnI%_6wqJz09f=I%biPct75An>kVrXnB+@co}q?okdwKD0HR8C^D0HxrEQCL0}x6UQI!4Q^&WO};V+5fTe@ zFfC$+;+LR>&{~;OnYX|X@{b~e(k47o#YAmMtxhXeyG-YrZaLr;8XJ*~xDXo%RWl~@ zkLH&w@D_uX7|Q~yU7KFpO}k}#AIBgk9%mXCq-%v+sOL4WBjCj$`ttd^`o{;@1wRR? zzc?6*33s{t=_=dx_UninMz@A;--vX#a=0{Vd^0=?BL5zrP55X8!Rrl_je*L#t$>=tW~>1ENc}b8pyS7%>+=`(+cp z8Auc-a-9rNUo*xrc5+Q{^@xawegV0XH1HenC9Uh)qKI+CZ;O8|aW)gSbdFPwv(D)* zJ^&@|>h15t?&sq#?5E?8_vQ9~2b@i1E`6@;_MHxTmPeKYrWt0fh*u_6rjN}0%@oXM zOiRr2j6#i-blY`t>M0tE%Ab{0U_W6763G&3qEAIX2$cxIAy*(`fECKgt;bypI49}c z72K-4VZ2a26TZiSj|9sg4c{sCa#VD)mv3vm8n&aD%GlFDto}^WocDmWq)-=l}3Y3ys}j0TYkGwwh!$NfVS4p#>%$R>Y;V9^-G%zRzB8t zR_Cl2%o@!n5!EILBS~Wxz=a#p`mH^u@<^3I>6=otEJ60A6iG@}^qFWK&~n7_6!Elj zta997$zTy>;AOyrtnh!21CQ^5`S29!H3_})i|o3hn{xq`We6UWivi*O>lO?Z7Aa>oMX z9*XUhV&}2{Vn??UwD4 z!^FwB+4#3H)3JX?p#v}ao4fhC@f`{sqn#@qH#?I$uD2((oon-GwP^EiCAIIj8H3%d zVXH+8b1P#DXQymOPR~(yYTrO_)Ie$f;^3J5d#h^Sv7Ffs@X_E!_ zea8??Yd#Y#h=vC&2Xn`T$5&}$Y2MK#(d{vgF|xAAvvhLoaAo6>oT_5Eq8$7${4>}cd20=6ozZF1VKgW;{g|H9qd5ae>A85N;&=b?alHlDCl&5K3PdT zefZxC_89)?&dcPa;P?4&FMcXX9#4a%#{H$q-Y9!l+Su@*o~QG$9WtUZECF%`j?wqf zkMMMOBTC<)7{~#@b8d1nK{O##P*%w+N*zkVni-mh1|JOX17&x&MYiQ*yD{t9WaqO^xk=71H{f zd7wqIQG{{2uAv@}W~!z;=;f{hy?~j_ZcKGKoG>xLAl#j1Vux5OlO@5Het? z&kG3)-xewt5*E=FaS=`x77@BEB*|~bpUKn0^Ny#R$D8LBk1x+vp8xFKV|Y_}djTWQ zULZl>fR`TRqLy=cbKc^7#9qiA#`>JKjiG_z3+*r3;?tj}A5UUWo>KfM`XE;T1AH)b zTY+0CXd|@F@^9cl9-HHwx;L3J);=mcdzL9w9+$U)*)CJ#as6oEyS)uc_HZHw)FJ4^c``v$uL`vKcb zyIVH4wry4~taHFrEfFDaB4$`(D5&#OCme8-`{5xf|0N|>7Mcno!$5Bn5D z0PI}&Mmn0herMfpy=84^-FiI^#fXYVt*;ucwW5;NhZiORtBG#Tdv+3J z9yU*&PSB2>8+{FOs~f>;XSkoSzr35$MeI=N$Oe3m*zQMNMqSpO5}hU;^=;#=e>(oQ z7j)FL2ex~+9k!BMPTH8;s(U5@Q)j0SF%&sS8B`m@f~+r>slSsNb5XNrk>zuj7AohN zQTJBeHXAo4c9M1qahGt0d)tIk@PJ^gMRMNrf z!2&Ukm<{%cZ^8X{5R;4{Z>4WVgN*PBlqG6;ITGa6RW69l$00Rl%BMG{+^3u-6DA5K zHz!0VJjT<#zwm)m?mr{Ursnp|DCc0yR!51L&)6)y9M%6#8UT4z{(iv z9*Q0G!rw43OdtL(-f#~|Xb1XMqhsddJEz{Kg|r`N$@I1KcUj_D=r}-fGdG0iJnutZ zIzf^ER#-{IRy;}kwIo6EU+EU&)go08Q0v4bk*oob(7^v9Y-QA*~ zVqpSGNOw037#r)}ciVeE??3RncIkuL9tPj|eD3?4bDuLYZPM~76QagXb(v-x>k-$R z&@f};%nh?D=QPdjnlD^{Neo%?b~&;l3A|uS*ZXZ8x_SSW1v`q8+IDW=-IBa*U&;Q> zDeF@t})FHGs$c5uhDwGQh|%crjlU-D(?f(4fsPMNWG z=Jn~R@kOy8;u4~~qaTktH+tnz)zBSIRZdx4CHFHp+x^p|YK(oi`%;?%n*P(6^K~P7G=T+c4-j55u#Py-4N3q8jj(;;TWfCt&8)J;4#b?HUOPDnyX6D)W zl!QssW8;ISg~levESx%L%A%;$$@`}Kmpd>JRT;f4dP_{f#J`hd;aL&PF`_YT!B<0q zVecI8o8=om zydZw0&1M@7VB#w|k2$R@E_)B;t&}ht84XMwvz^(*^x`^mv$=n`QM{eJRL*aX2Rnex zV@I=V=xy}-)Q{9wf{0Lp%fpSs48*`2k~-^85U+dEX{xvcJebJ;}K6f%Kv2-M94fX=~p2 zrpjKYzWV)Y&&zplxT*GOsyBn*_owgp7?yDie5-{&^M54dBxVoIkIuVX`1;@dvLB^w zRY;{(n`b<2)PIx*kjr{XLTOv(sb_YR(81x9(E7zc?=NyB|%;9+}?_wDLt=ydA)0Uc zUSRH>+?Kz|xtIRl&;9UsW!|dX#(&oT+)EOQ11b{BbE{mc=GQ%~O=>8u_icI6{HUGQ zeyua0GqI;rP%7LfOqcGG&R3F@RoX)Bb;EkYWy?m(AZ!3ufLGviK*vR(GN`ePos5;t z=S&&+wn*56I703y-elfx-e+C|uZHW(3kSjFGj1dIF6S#}9s36R9j%47g7T2kMPL(! zSR#%KnZc`&SVXI58|G=fwY@N(l_0m1dku7nFZ9OsZtt?|a&EoYlGiY#!4uq;T&t2R z3o7}Q4&}*Zlgd&{SC#xKK3~#Me5g3T=vHxW(bA$Dg-wNn3O$Nv6kRVe7Fri4Lqeu= zA**CvaZuTr(s=NACe-b$l{T(!q(F*Pf9L*AWAEqQc<7TSi?QNxz?SBN?}$K^p~}@| z=!m8^xG2&4r}q|TybIN8oz-`eh@{XP^|K(|lGKvi{I<-oEalJJ z9G{OA*~?d0mnYxPy(@X~ z-5E<;PmAY$;`urrc3kIK?>XN`>!S&{KVoq(KVd3ygqV0=;jII zBgSyYDuPo(G%$17<9BG-z9A!r>OCKLb-Jx`&vF^(dJyJvi=1qn-#gp690zTv3Ve$) zT)bQhor7GW_#gQ++h|)y>uuI@9>;1w_bK--U=%3sbnY>36SvXY+h&S&y0wG#TI*%J zx4gBSEY54TnC-%t%7~yWq6{a56V_u-Vr6EdS#8uADiw7?@KX-av{xTp{_v%&JlDFsI?|+x}>BPq<&^n#{{p8ok+&O;- z6)ek_l#DESQc11!Zb+{0Z(Z7&EIe6X=|+2^v% zv&S<4vYE>LPX~ktQi9HmiXVMp%#N`(;hYFQWX9c^ax7}rRL^OoSbE&i>Br+gPX8Bw zE#cz~^K{>Y|2$OPrbbRPOd&O+Ej5%{kMF@pf@d(pY;V48xNNuuS)zH6L3%;4T9GC> zDH%23Fd!8^6OI;Y`mB0C^ppxV3Fbl;@B#2J(+Um?wh8cpG^mNs3tR+WdS>@T_p}RM z!F>87!DvA!=$rN354y{{c63#BF9tT$2Qqv!ySu)|2e)tZA@5U(f%PmzO8XeV;4-yRq=azx$98+5uQzQ2EiaKNVILK@BPO zJ*~;D37vwDQkW;+DX{8s(tkZ1J1%lS04%vvEh)t$kpgg0NP!s8==xbTe zS!+R)H`n&D?Fn0Q+U|8yI;&tWwCQ;w@P*!Itf;+T3AKBq#5mwCM`R0? zc1lmJSwq*A1J=CMaL2gSxY9hsECydwCo~IPhW>|EVoEU~_$m0K_-FWH$fxkd$Kxk~ z_TvQUJn0+Ezr=&zU_E4sEu^K;qG*e0QS|NfTeKqDFX+|pvj$jE++Ey{+#+tZmD%bI zRAivF^p5W4*>h8t3t04#O1zzeaowS`(Z<`pg&>;M387*F{E0#yC&+ zi2pwG;jFa_jx8)mL@nL8{KbmY)%>-8AusdyX7{Zd0auRMUbmluB_b`cKamn z+wGYgxLdU6K~l^v;`X~c?AO2B(6B6F#fYpFVdo`}nY*~k*+4El}eIyIgST8x-St59aon!;6O}jIfN@8tfKA8dWfA z&A72)JHqEg^hC5oeu{iI!70Kia(wuL2p|N)C}aMPsR_9gS{HmRL>P2GI5@}{^e1q4 z(9!@)K$w4{f3xp8zop(vZ#&2waUHa8&^GW8@OQf9#CAO9INtt;eGLB(Kic-Jtt(`% z8SGZ`Kk&)+uN;OrhB_8GvYie&ZE@5&K6I2gKD8IxU*fm(6Zl#Dzjk!~Bfh)6z%HE6 z1f5s6!xTpcr@ioAv16(|q^a>c`6V`EZQoegSeLVc*$e1z=;uf!Bmro57#K398u^B} z>j&wdC{vZ4(jU@(VU6&qV2j{nyJ!29CPkxD?cJI+;Nf3aG`eVBUP`XdpO~D+U!-5o zKjOcaeQgBwALrBGkMA?+AF%H?ykGiGo!*&d|6ZS7_x|O(h>wFZVn2I-%F2BDb=R+3 zKL_WG%5H${x@C~dcD=m3>;dG@zix7BitP}!heBqql{8cGTD?vE!F1gOA=ua*v==lb z*N4{uGv=4wi0)jkV_qcR`@TPhuklX`vi?1(pPDb=gJ2_pCXkMNM=je5}Ej{SR?)`juxv$TJclyOYs!(LSE9`tW^Ty&rp~gRbB@_?*P`R0#}S2f8-*IQO7?fAn|={&b@{&b7a7b#Gk`+0Cu3 zn_E>)dz$=e&)39NXIBlYd{A+-w7Eq3FY4dAyeqkX@-=yL3-06x7p%$e`_rFOmD89_ z|LgjP{deY{{lNLf{aWzz%I~MYva?gOY;%YF4gIq+=VMMswtw#Szds8_|BjZ`mrk$v zR{o;eq54HVx!$?`Y1`ZG*zTEqUA-TK8-z&`FG;d0MkUe-bONLid0;tW`AYamh@5af6zLr4_R~%6e!(Mq&>Byf`yP)$?wRiCZo%$G z*K=;Eu6}N-9rGMV0`JYRTW%+_U1@jNdcgW0htJJr^f4|&9xIA6h7v{CO8AJ;W1K9G zmhH%PBw4pl=L$2wTscm@8nRFKi4(;m0qb8O3Kg{r9|_IP<7 zQ~RcX_M%bvR7ewxMTMeAqB8L9lSwW}EHDojsv@dPnoi9`eYn2d_{aDd<^_|{>(Tw7 zW0Yc@ad!#r1R-%Wshd2U@(S>rx6~r)8Tt?UXvTiVD%J;96zdTyf&GR(nthu+6Z+yZ z_At(7_Aj=MmB?1H#<5A9`5YQ+1uK;Gl9k2ufuz~F>?f=q(Dt8X-DZAbE@ho#N!jrn zPwq-?Ansct17EgHqAEg_>T4|pz}gGCpgb=YjE2;$ko#wa)?rfI(V1+?)94h z*x-_pLjz9)9SFvb`Y|eT?AUSp!nZ`wCSfM8nX)yiYih}~k+BYOY4HsSgJ&$9`Cw+% zte{y-XV0E_YL*{(0`?}zW;Dd3XRu-;;!Z?gjhQ@Q*2L}O4~K`3@f$le*d`NpUx>mZ@^W*K$uo&!9#4p5cmgAOm?KAB@>1JuB;Hto>^+$`KW?#)qNclh& zEY8`kLpjA#mmaHhqO)RALhY07n}*^!q&0Avzl!JY??aX?*P@|bNji&c-0&YXBqc3x1a6CiHFzju|KotaQ1T|fp7HS zOyKO}W^iZ0wa~_6S(&*3yo;Z81uY{9@9X-iCyv`3D)h+QYnTge~$eJYV?p-=l)k{1XK=`4I(+^4AqU{dc(V-#-FqvTl@! zivQDatOJkV+tq@qjCyu`P}74(e_)C(btyYN`fm3&i`R-@f% zgNPw8b8(B4#__Y6X0sONr2}E6X`cHZ_hFttJU0w&AF2Z{pV^@A-UD8-FGfrn854vA znS)*hzYEzH`Zi=~Xjte#XjAZ|5KM4MaLvdIf&Kxt0rkUo`HzLH@ur~%ywknH2hSVS zJ?NL~2)FUhmz`HQq&qCLy=QB*nr;1;-N3G3_AsNUQ>Zbd@gxzhAD52)iY~JJuv8)y zNT)H+*sZ&zyRKWN8>DGc)6`|EJmo<^8LAaSlqN-wJX3x^u~tzcyDJM(bj!EOCd+<; z#~w!fKom8g7q6Fcq}+k8;yGfGhz~gTKgnFll7VKiUU*hm*%QveZJiy{Iu`c~2*!!(`g0_)l8^Fba<%e^QVkl%PskA@4rPm?Vg%?u z{8zk~jHigH2-S(nX42v9)Dd#8+q)O5k{6G8<+yvZF)B)6b zQ>-Zpvivf1$8<}yJx@Q^=x(etmw^BOfTalY26G4ajLn3# zgcqcDBsImIN~62dRdg2PKKmsb#c|~%^U^?RC*p0fF0`(+_OYq4akTwyOR|f$dui9r zpKHH{f0v)&aMR%r-_8CczX0kk0^c9>unzp?e3`9}-BR1Hw!3T!Y~pR7+4fqy+MKi2 zTW5gobvQ4X7Xjb<9Z$$R!h6Y^0RDBh+?m|B?0j|z-1GC<_t>p$D(4GZ0F0IpzKVmd z!Rm=)0xefJN5r+}HF3-w6W0UKs~f!cR-IOvR&7=pRtl@J)(5PwSoK;-ts`v;toYW` ztS?&sv{G8dTc5Q~h4*c@`e-%V>Z;W#UO7+C3$*&qZR1|%7IRS?9;_l+oH^|6>|3lS ztci?S3M|S5zUGnUT;ngJzfliUPaX!bzD7S=KT5BGeW3%il>aolHCMHhwK{d4TCM&I4oy|6 zm#XcmMXCv^iO?h61rN?+idaQAXQEWMk@2x{y79GP zCAhu}Gi)}jHcZgd_5KFAeui$iE>rhLcM}{xFKbW0BVK*V^-Z;p zTBJIq9uH2iGt}qd&rEfoI!29C6Ep>o*g;a4X|`xKL!Rp#z&?L!&qJ3q9vno|wR^Sm zv|IJt!E3Kk7igdvk_?+*D&U17*Z9PkWL#);FriJ4kOPRfnPQ%Ab~PWjECt5(pye@Y zJ<11Tjk$>4gEnBYF%vO^F<-IIuHwiRiVYorKK)gHtDDE&W0F(>ScyBmA z^@N#(IKpE1yOPjCh#*7}SVRW#3*iI7pXfp?$N#_=f}Yocug9Mx>>^N!4TMtYrPXlV z*5K3e(S*?i0^SaP0(Tbo1)GJfz~(|4$RjwmYccb&!B{oA2)z!o1Tz3$7fh7W5{HaI zO0}1?XVr_nyi~nH{Pr>)<)IcukCG^ z*f0TjN2lt=RWB=Y%c(WZ}*xrlKC%o={Ch!)7_ZIhz6xWHihz+7MFa!NmGDk8~;i`yJ`KxM__m%(E z0vExPdlfh)&eo37TC^ybAi?S44TB8tkqZbF5gLygRu~Xnp3WC^)GrN}3|R0Mx~My+ zQ^9kwMz(PtG7)h>)W!??CHkX=-G%^zr=brr5QVxL@Fb(?-fFkN{eDJcqbUMK=M`0e zR;fwRcxtBUtaK0)tgD85HO+X@7zy*}D`DMdA!6eJWC-~4ij7lEekPu|%Y-$zn4Vga zEOuxM>Ns{U)*8>lZzOI3=CqA?1hPk16dWau`i8occABQ3QfRJpe|kREOkGDmP9H@N zpnqkQF{U$jFrTtYSxea0*+<#m*`b`BoImVt_6|-Cr;0^n&tNZNPh=&s0$9sg&a81P z8fd;Ag6?lJGn%=KwU_mP)y^8v-ogIO#Ier9cI?VL&!n)Hvclk%B*quUS>`Kd8AHVo zFzs0ahBb2=^C9y%WT(G@&*m{#GkqD$7-Z&X=4i%t1{eOXRL~;mm;{zBGZNBA?O7!( z1M4@llZjysXRT!%U`%0dW!f{wGsZI3Fgj=^nuJONyk`Y1k$REZ4!K*t)bZ4j)L808 z@)`1E%2bLyIgosWw2gF@c!7u`FbSFXr=S`y!{q|6#=yE`OW+E>0bDL0>w%qsafb&U zgP@%-B#Z>zgKk0p*TY^w??aD7Q_#`qiRep~O_rmU?Uo=&+)4;#l=GC8u%EA2u2(V?7)892t~@WFE-#S1mmQUb$oi!xrC~A=pvunD zg)+X3B6}iL$WmnoWT8-N*(z!jcNIqES0!I9QQcJSRY_nUsZ>8!kJa+DSnVf`9#*@- zhE9E!A=&U1IfNVn-{)hn?s%iPC$U0pT&M{A9vI;!olZ z;(nr(BqvQHttY(%KiAWwo20FzTcChIlgB|`5RO8p9EIynPNq}XWGC`Y(tgrcqL>J> z1Hv@m^2In2ZUlZJ-X6=qzQtU{#G)smV^P7VtCowFqp(u9nJP`QkQvBn({7X5C^tSe zE-;QWnhiqz7rn3EN&ghQmtAx!?L3XUW{zgCW}4bTyf&HZI#}1B|Da!_8>#a!=%Ir) z=_VUE2E4(bpJy0p2tjP1woWx}F|9V$A`cL$DaT|n<(u|_x7T9J1WOF~c{!mhmSngF z)!^}U5p@t1jvj*!#{^?ASP5n&b~?5f^Bt3nTYx(b8Q2xL@3>94|I&8n;)dZIaA&a> zv45~nu-7qHFhjBSSU=1ZOd+NZqe3I-Z|FMoV)SY>8Z}@^L@hxjn&+A)m^@6`hI58e z-Bn#CL^yPpQ3HbEjTicezei`>jq@Yg@OsHmu=n{raY_jc1$c8>=Dn zGP>zmwvzUl(CzlNjp@j0U)a^z*(XR6oCM6My=Pw!L73W?D~uP;5vls$ z0;al693Va^StO~ET$K3AOj5k;n=~BmxkRO-^0{iaYMg4Q>Y;L_k_)>+w8}BGnI7n2M}grgl*eSF2Tvz?1W;CRWp_ey<*-c2hruitnIifo79twC130 zsm>MFi+KHXeUTx{@XEN`Sc!Z@ZXrp?4D&=Y8y*v(7rBYLhkA^@jy{fAhl#_^!cNCW z;A`<+_!Gnw;tb+sqCa^)*@rxnjG=f@R#W#=HLwr((x=cD(dN?F;E_aOI53KsVrC^% z$o#;{V@X&ZY#Q4KTx?x9VeBnz8poS66lN-BaSn4Dfg}C^o|abJ@!-h!hnoU|*v0=o)Tw-g36EAF(fTDme?;ui2|P$2iYf=`15_ zGTR<>4_leHnDul8{UPlO&5AaR2ICi$0Wv~vf=tCd#3#gegkJVJKtjDmw*aTawk_>u00I(;EIhP($?>qZ}oG-7mv@-#o)K z)bz)6&6J3YMjDKD#!tpH;|i#>k3fyou6w6z0H0}%=A-7X=7a{L=>m6$1hu1Dp>kIC zEB1iz_7C}S`F`-+9)NkgF|t0XRGKDT3b^nrDN*Vs$sO1ragtcXzr=3`62axy6I2DC z#2EvN2l63D!eIEf@h-kJSS1D#CWaIV9j@SPk|T)Xo*K z!luCNTnAc$K7x4#$4-LUh1!GqZ24rlW8PqPGZV}gkqt-y%&R8q=jwMuHu4YUOC_XT z$r;jaNxA5R=udxg|C+vWeHd83#Jv}LANEe{tpf$hhpv#Wj~ydB7PX05>8&SP5*wur zy82V~?`sCt&}ueUZ>}n>jIR7z@d7epUR3o|j;TFY^SXvq^P(1AyS}!eW@c?>&H9>3 zK*&c_O|Sk~HBdFJY7uz+8fp*Mw$}&NuWl@Fm<<}w^44jsC&5pT+;t9gGFQ8P_ayW< z^w#&pLw4=fzK}ka@LS&j(GYM+ll0$$Gt@Kibs$}KLiSsJO+Ho0S30QmDzoaB>aivP z+~9N6Pr#2@t@)`DYE!kNbpg5s`r-O5hN%Vxcn8fedKm9Phn5I9M;!RDoiwa9j5Y=s zYm9GQW}|1JhvUcMX_R3UcX|YU zJ^K!OIByXz(C&^MA26en&PwO4ZYcMegA9YtfuGRmAvr_7dN1|aK9uP_%sbmV+Plr$ z2~g?l-h+HLd+WXB-WPpdc(3+J@^SOM3mjzakmEzwc%cVd20iukaDVH5(gkrj4qm)5 z_HFiMcFXu-Hfc5&tVPy0c&$7UH-h&EW>2oMTiAP9?^##rHT3ng2Q&%Emh4EJPt1XP z*@!`72OvL60iLFZVD9pY`2@Hm{xTl{udC$t{1|+}onTU(2iKv%pnY z*tcSUJ)n@jlTHV8qeqvo3o`|oR$69RaaRMM`n9O`;!i@j`WV8 zj-t-Zo!HJx9lJWIopZYSJ2N^HIy<@!cWvlIcN)5Ob?xoy>HOU_rR#HdOm|lIqHc-c zntdl7QGP^Ia&k?e14hb#b&EW=aW$yiJME0UU(U1OJVv=~e7%#2?w~cJc z5{X6nRk}!SFaHgDQMK|F>_LB(|J1M4BQy>gH!Vhc1iX=!=>zpW#&Y8z@SHkh-eu;Y z)dCcKLadJGBFqw3Pluc5QBB;sKr_=`Ob^2d=BcqBTWqE_s?omz>_Y=3k%E7wL z3S<2l^4Kx~XLh!`Wp~@I+wQ6zi~q~chM#Qr!OoU{lt07njNNRz%XZ^!ci9Ho9<)7e z-EU2|O0=@Iinns(q;LjJRFxfpplk9+BWSC~)Gtqd+%# zu@lv40p0dL@PwP$QP*w-zN%xOUnm0~_VD(ywzQ5(9bbT7zuOto8P(a>v94R*^}BmX z_jZA-JHKaX&$V7|Z=-OpuvxfD*df{~$`z-G=fh_@$m*r>a-(dfQm5Fg8m@|mjK31i zU5x~MMD7D7V*}PwEaPe{KQYhCc#bgTQ zH@+7?54#o{ik^YKk9h(=O%CP);K<#UI?Dpf7|VB59%>PIo!`)>=&wOVWv@c17Ahl^ z>tqvU>j%OHEGJ?CJ$omueK%Lu$9y+^l8R23Oy#dRyI7Ranic_5p-W+7Q&RzNw~> z)0EUWr|ECw=a$G8b<4IEM%$g%mu>Ug{&vmi8V_zN_Web|&0UjpvMsrZFazrQfVW=}}XlcioIj!nNaF2}22!2?c;*d4Y#SIC%}( zLK;R6CC?@Qpva*DZlRo_eFQwqpEilUn*J7WWIO5t>Uhd7%4zaX@(noNc|-xx3(iwC zeloB##rPCJuzjH)3Wq+p2g|@&a5i`c$gobp?ZY8xCMFsE5RF3dP#%EwNX!G~$L1^M zaEqgyPy$Kuj)0f5Np#XZ(pS=C(ge~cawhpR+*dCt zzbUTNcxpLCNI|F(G&!)ZLV1N;35Xbp_=(WjFOQ z^*nS23Di^68I%K*ai9UPr$kU@lW&saDC;SnQ0qHUrcwOJi{X>Z}oN zD)|7pkW@!{NBl@UMtn+~O_)K@5@du_nEx3?h$jri2Z1ur0IqNS_!yX5j>L!I888Dp z0LM@Q9yv|6Z-|$QK z45~dVU5lnj6AL{+mg==irASrSs}Nj1q=HHV;Pq6VYvP@d>OGzv{XKgC?c zWCHHK5IYU~9Qy|Q5PKE762B9=vIF=Bgx>@|LKxu+;T^#n<^@y1V`d>CnLxuMxLNp7 z_~CdGyl+1~0yiF#dcWf);1lsVFn_oMc$6jBOV~re3zeWd(F-w&m>K9@fVc1f(ZQp> znO{Q!cNcg!uQpM@i8RP$WePL-n0_NUNE;l_OydjVBmHfC5imF_bbf$FS7|$7MLVWB z4%n3xx|n%tgSt!oUUf$`1d=AFsV1ocR1PYtvKEf;1;tIpX2l8lWqH3mUw%L~P8J|1 z%Dn)i-zSZb&VYG*iKIe;hFQ-e&`&kNY<-b9PaHikVZcVr5&Odo%vdp3JVAsJEfJ3r zR_ENz~5QHfs98Yj76eh>@B;O_e4c_TRn0;h;4A>JRGA>KVX| zMgYf?0e#_Z)p}JEV2eAIGn5(N?Np$AsO(ha144TZkRq9UC2(-1@&fr>MGzd{#qvAQ zU*48akl&HT$S%lZ<*_n_R4BVDYm;7=zLqTpHv_C}lgv~0L6#&lN`FfmAUnhb+!Dvg z6;R1|%5vp*K^=8K{zHC5J_`EOK!piBX%;G%C=wM`km)2>)+rULSMWQR!XL3p2wHox zrcC_|xQD?yrtXw}oxWC|rmr#l1D@%jQDm$)I+|=vlT2PF6iNul^*Rd;Z9)zG56=e8 zZ45RZ*4?8x8o{4Xhi}EdBbE|X#4)7lfEx^=EC)_cO6j3G((0);v_te#`catArZN{Y zxy(f7UzUh9opqQ+W>031WAA7C!KC4EPBP~g%tGB`_pq&5F)SZ;BHM?xfQ1L{U_G-C zu>XF>7RFb`GQcfP(FC-UltM}`Ig&D(bd7`}1`@Tnk@&gTmDnaU0W-?73w8%CXbuDj z4Y5bo>UZexLw{^gx2qvyMj4`@E0)W=Wx-Odq($^p6e)5L_4a-3D}q@7bk7Gte7C5} ztGl$S@iu&0X3MyiNiEgQg67T5InDE$?>6(Ao0}FkWi^L1S2xXX z8VufF51I!zKWp)B`PnkHC9pNCWmg-aZBv`2HLWAG<6;N1BdH75Rn)bv>#D$6K<|Ck zbG&zW?^EGI;r@Q#{;gs^u^teKY2eYIlKzp(;B0PF4pp92El~YZXR5bo=4-fmM0Z`E ztUqVmVazhVGCnpPG9{R!&2fP9E=EP8W}`==S-?p&Kt|(k+-`7N-HsbdSO6&c0zxD) zj%b3aV;k`laUba$$q5j7EjUW7AT1|hV1J>Kf=J$EKeCdIi z{004jzmRav74&ys>P89PbtiOpz+BFY?(VLOUE_g6eAXS-?a)&%s29Wugn|ul9&YqR z0@8aNe3v%&ar!3pYlOdri-n1z;i5@mwP@S`X&_M&Byo{eN&bQsB2_*c@Ch)|0gI)s06@L|LH%P!cp6_~t9i_XZ+2s*Qfe zNk+MWVDtu7WT*a+{yA{XYv9Yjg>mf2(llN zwUUWaI1~eo1N$U1C2f*y$!W0{MCz@ub?MpUNNY0WyOzFgm= zCqm88VK5r_h&57e6dSk0J;H}M(39rp=40lY=H-x7u+?(fG9PsevI<(zGtr6Y|9HG} zn2(sLxUF!cj+;Xf1d2xp-#%puef-V(|P3_=Lu1R;yi z482tr0U=z$f5YqXctRdti_gSU2tvFc;UA8Jr@`*7f-~j;{K{SI7wipe26hYPB&H9| z!2H5gW1hgddj=eNnMGs?M9oL#fSzKr<$&cKRFsXTdf*5H%mMK8|1dR~WJo7cZ2Dq) zi=+did<|KN%tR&`hZs9xb|l5P$LNBv5idxr*V;u|i`@rQ^19)o@s4pL z)H7c}M{wK_Whe*V$Pb3Mh9b}wEHf?#ZO1s{48WJa8Zr&1j7N+(qtGxIF&b|JcMuQw zS`p|r1B@e$hmqaLE5OdrLq+t%SZzcj{l*l~jJ6t>wP?mH7qmTQX?SPAKo4q!`sktIkm0s| zKh&JgfXy7$f7HFu&Cm@2#;y*yIFYtNQ>;nXJ^~E#s|KTmT0k=q*uQt`1%QPlszecYP3Vzo2z;Q-vVxaa@ zt4kr>Y^!!As7=ObrMhNaIOP1?&>aQVs9AeTH%qq!a>W~Tg*v0IM^^#!a0m6L^!Ig# zb%pw$`cBXz81x{$(-9zn_MSdTzfT{gpP-lP2?iHE2b?eN>7VJ+b$9fK^;liI_Ny*O zw^6rAhu5KX|KRy6x;xOmIqCQ7cI(dSF6j1whDof^X^vvQ5)rv2m zMNE}~^;Y!*^+WX+%_CU-XKRk=7VFBi<=TV# z(|TKjkKw1G)-Va#iX1~eBOA=y%?r#iW*a~WUIG8%jtPV=p%>$fbHyn@S4t!V5h`GB ztszyB{3v0R?UXkZDb<;_n)a5qhJJxw4=M&M%w1{NBSFiS%I0&EV7ht?FWaiw%G>&e z^)IWD*3)c0+bpojvl(yq&W;3`6mI-`dtIidb`aQUOlgu zJC8RFJ|E2vgZZ_c3=?$-Z3+24`2jJ5=uQ|9!?>L#r?*%;cNph!sje3dkM3G`4qG)r(p)^0i&C-fOdmMp)I2g zrmUt!fY0IyQUR%--~lL7FR=-(pGk!01Qjj>zYg~W7lplxMbJYqSJ8Rs$Ce7qLrb&e z4D1(J*h7XQ^O4`6;VU*Y7-|8}GQoX4TFck&1jSjox&aiSKI$Uyu546vL6>Z;Xp()B zJ&~Q2{g$Lj%7N>?JTPv~X4MUMQ#LdJoy@ucCEb0eJhq1+u#--pM5ef-r zB9GJub?iF8J3dh9v?q)PhCMTuIfwm~-OQfMna#b;J;x7(_0F?QGN8f;_jB0*y+v|VKPz=mVH(zf6By|s%?wRMEe8qk0i^CGRd zyj8p_;Hb(5ACKRVp~PWrV)-zV8D{!4h9m6+4Fh`dZqf+yI^q?;`^1FnxC*G)v^Zzb zOdLmlN9S9FmN&o&P5=(C0Cr3_P-cZ0q7B;&Ck$_34ZaTbunTC%_G`9ltThzS1VpNv zp!VmgxT+FW86Xz5u;M>eUQ!%abOVNgD0-l)O$9!5lX5cnt|chPCvX_tZqSZ|nL-_?XL5B~-CX@VaHTWrl?J*o2eRHTAb*ghPzy|geSM;Ntoe`SrzHdx zih6^}LW$58v=zn=G(ojkBQ_kj0Dl(h+(i6KLIuG{7($#%x*4-6IAj}l1T2YA&8 z>PcEL?Gm+%>Hzp0ik?8H!i?-F#&gho;h9wCM&^1(24f-fCo_U^laau@$4q8qG1@^5 z_Lfe9-tZ|XY(Fs8(BIQZ^x5`tQ2;Iv>D=ytVd_>%JY% z#w~R;nD+L>Z`}sz!rmWD(R5vov9va@eH>+8#*Wo}#VSwdhvsw(3gt zfAq@?Q$Ve7&G5@uV!RLcaTZdG+(Ql`_e~E>X{LM7XH2u~g}SyD?vX%LIO;igeA!@z zVe+BIEyfIB4ubFIPV5V858%^tajS7d@Eh@SaYt}fcpHKz%t&TK^{T^rK>gZ@-$zIT z$GEG6*Tf=X6M;y)1f0Jg@gT99s3nSs7}8_nZ_pUrBt0e85YtG%NPmeE;$>1gi2;~) zB1~6qBmE{lB(@NhBwzANVil1FK3dVl<=_l^ffx^{VFuwZD8y3Xvr6#G2ulecaZRA| z^2S$SyRr8{Ke`@!8f%U5!W_XI#c)6iZ3h@zx5Z*v1U0+G%(N7lYs_Xd#u9H@0tkhZ zX}mGacpWkv-#|V0!|=@TRDWN;QojN+ckb%BdiPj8fm!^e{4(gDjnhDxi?Hs6uRN7kYJKa5)SlF%G1UV& z7Tb-{V&oVHY%FFO<`kv~a|3+~(C?9$29y@H2Aza1K{bFj;V5c9>H#VS6^ptJIUm{P z2J>UfKT812Ch&$qq+=N`tAjqeC1$V&&(EU-M|G5YKPy_Vb zQvqGLhrERE#5FO^D6fA=Wqt`pX@Lm&TXyTDf5wbdBvs0&)nljokr> z+hW{ed;=OZD>#c=O>5z&C_)Z`{-@8RF~JZPTo+1M$4X6rW|^x^g>XM{Es?Od?6F+2 zxLSr<#z9wn&r)fjTkI`kLF;}QIzdbpZO@^Po}Hny7#~TsHqO-2fzFCtUTOrpd5wTt#jp2aJ!5Gm+&;nW5iMU~Ge4 zJ=G8mUAxFYGg=#|#`loNINz|`P;TroPBM%!oHSm5y|)J5v(9+OPzj2#0z*4=;1VMT zp_qJ4o}l}VHO(<;5H3_pp1`d5nKpx;p#YBXU*s3kZE7%y;9pJ_zUBW&ItTDL)31$> zVzdov+upXRt*vbvTie~*wymwU+S+!LBF@-0zhD2~l`Bn}%p{W+&vVYLGdwsVxGc1v zBik)x;U3) zUVVgnQ9 z3v-3xoXdc)EDnb@qItr#!VWz9i7*4DbswP`jLjl23wnzh@H^`XFOmZ=UbsW}jGr}H zxJ0;CFiFr{Sp9$8U1ebv=8!E7RVIr_9g0L>^@Lrf8hoBT=wJ=M6}%Fx6RICt72Hg! z?(Z|YJouP;x+X`hT(DA57-|$M8Y~@L$9*Is?^0bwlk~lUlHMp9)^3>qrXavaf9c{~5H}5 zqGCL!t7xKdx3CNMVK?D6;Y85}?w{tuUvPluh40Z0>xIpPJ%u%xd-PEFS=a}S+z8=p z;XuJqfkF@=Y|p*48|B;-!CFDpP?!JRQ?a44talXzO*n4ZpKm@Ud=r!6k-P6hOu_30qz>H#R>I`LKUU!sMgR^p-J4WcU|EBJv!6ff>A>Lr>F z9{d&FBTQU|k2%5(qNS{IB}8T6m)i;F3aH@~-{yQy2| z`sq66IZb6h-ZL2_=%Q_{t%{?RqmUy+zj4^!%Tmws+_2lwM<1=vW!_N{!!P|s_`5m! zQTil(CbO*4jkU}sQ$tfZQ%OU#;WuB`+L&ToX8q1C6r8>3n z-?||GOm5x!RQ~+gQNFF7lFUt z5OX``Osp*S-)MbwVR&tKWECd7hDG;|To+jboxlM+OpoYDDn#^+m<}I%D=a-M0uMuT z$rOn}R7Cue`{yY8%W$q0Qz#JnMo)bbx0Ng?50(qn4$VQ=wLx%A@KzvVrD-Ny#U7*- zxCPa@&O@O>fP+p;+L{`+nnENrh=(@Q0YR6WI?HxNa?tEOQ_@(jIxTN?x`P$}d9=|yLV7^EBO7i)A zJU_1Z*7*v<2gcDo{50jJHOWd1jqD90v#1F(XBvU1K#h=7Y!up0) z=9m{ki`tp&%U=?co~P&`fCRMJ)A<@f&u|J#;2IS(j z%Jekj?L9biF;_wSkl$QYoON+Hc$ugyaa?ljb)3h4JQj6ALA$}$0RHV3ex+O1z1D2o zZreZBRbU{^Y@5JotJ;!nZXB*FZ6j>=?K|wf?M>~|&^hdPY;`Q)nn-pRLs1{$-sQOq z+CR;6iTO9{eMfzAzm}TpgnuJ@^eQU0exMrrLZ?HwLN`Lu>{2hOyw{1Ai1x9j|A5~d zBwk2Iw?%v%U)U{F#3v;N$wr)E4_U7+iB&knbbM7Qo+G|1P7^0cqQr^fRgznh#^f{E zIGUTptHnaj|7VW(Cw^v8QG1b3kR+6dii>)4>~sPHQ?|m8?-_w5g#TBh`dEk0EHT)e z8YLM%CLs_L=n)(h{5x?j7#3KE_vou%MK{p_UTU`gFaJ#ca^Do+5jfw`zTv*U z{z?8QUw+?szGLFbvGuKRD(gG}-DLBn#=|DV4Bd5o)>$JHQE zmRFaJin~VO8vM@gz>dHhyyp*scY}=uRZ$c57o25}J}$UNwKRj8{4e1K!Bv4*7$#aT z+#)Q9|KKw9`%A9-bDWtpQIdEcn$>prA9dpLk}Q#!E4V%Gr^~4B&x;pxPu>(g77gag zU(J5|f_tVG{qH&972#6hM4^oiBTkqsoG(}}xWg>!vz(*G!v4ZYYUCP%HiG)p)I~#; zLxVy+LY=shio-)SgM~cJyxec_WTAjEFv&mB|B^SoKR!@7&<2L@k3bS!+BE+v{}JC6 z-!b^LX1*@ISbueYci#kGgg=55*(!dJAD`W4g8jed^K&0%`fmBI(H+ZuHs55tgOlL7 zA_LU|_kQ=%{xHAO9~Rh2ukki;5Oq*tTypbJ0QL&34lWMHP_y>se@);g+Ov8N3oZ?M z1LcE#`JO0PGuV!*NR0=*C;4y2V3E)Tj`?b8pR8acHR|QiVa`R@&3q27DRFW|K8FZ#M8=;o3|6~P}|q?wjR&1e)|<@~wm5d?UdSK&>2BbJKOMG8@Zq>f|) z_hfJJ5OGm-R6WEC#LFc6BsTW(7)cFDEo$2i)JiMGJ9xZKv{rmjJdPEso_GWwcd$C9 zk^nkW)Kb)kov<^wQ&Z~bcu|HRQCO1Q{Rr1rGIwMz!C*l;_2MV`&jrEt!Ed1qSjQr0 zWJZSm40gm_>k8BhR>aY{H!uLiVHF(hiNF@laEm}~d{@ip5NdILItE4uwzFR-1E2A8 zpZ5R5wb3|GH&7a1cjI8;;FG{rI*1EgFOU5*SUb;f^;G4Y+~zzM4;JMdb%Rf}1>RDN z)CtrM^b0Nu+5(Qiso>>cD{z%*fx&@poY(Dv^MT^@6~A$pN%SwL!3TZ>yn%PYykK@f z7RZlA;TKr$ChqFW^o-;2zkh{iJ`eKJDbP01)Zd78akc+1-$ZbiYW`fG!8gS}#xLR8 zYUQi#>*MPSI#3PQ@!!76RF}>*9w$eKt{JVqqr1!k{f#<5{zIUg0qNg2v>{{;vIN4&%#BPiWcDrY$XRNok z_lA3^`?cq&NACXQF6Gg>pTlJ=au0NGA>ZO(*BsYJ_dEA7+?7(-SF~<^XEi)JPn>&5 zA|C2IkLR)qd&+oMYcyUvoe?gBb0yh%_tA0{b%r}{J9atV;OseMp9f!>O4X1b7wizn zHgtz0@YpqU`aoybI{tN@0H^)eQNfw$+<+FLsxyEev;$mVW#>o7f{)QVI z4!@e|lsgx@Cb-JF67hVtakH8VGUnHj%RD{bI{Mmys6;hPxyBE$}mGP z!~fA=)nAGG-|r7ck*{0YOw)eLB z%wkJ$nC&h{rb7o;{=|8}`5i4!H+;nh-5cCR-R0e3@UA62FnD%emEXEl+NqUJSw@e&cbMcOOvfpgvze2PKC)*m@ z>e>}(3X0f!I$AkyI@UWnpzVC?cu2y>bH_Md+jTTk6>xrMI*6-&Au zTz?l_F4iHL`;Pmn=P&-+fhZ!Tdq#Mhc^8A3YJB&7dT*+?lRt^P2a9hj{N@_wv8)G| zZAvY+hgJAx@KkUkd-a*%Dsb~ZK;~8k^Mig408`)396MR?U9dg7(K#?He<()q6>Jno zeCP~%|MQ{qq5Ek4Jt1#swP1_jX6R+e2xk19{*R3l-|q;)LbAnnvp+uy{Tpg7Xdw6r zqS7hU4@6{n=vwenuvTao%A2C0DqwnN`F&Y*za!A<48-r#O3+Dgi8@pjGKacQD@+WH zf&GXHeGV#vbEyk92hRr2gsz7U1aAe$gwBRK2gg%O)CWn~8PWw~z-*?4YK97g3edaB zg6YAx{PbqvIKN+;koRe5IG^yketSi2{B=D-jYAbtTmK8Ay^p`G1Q<{j`|mqJmS6+u z&Npi1I_MvF2{uz{JVhN{88lxj*uz&Y32!q=z$X|DR`w6yJuJM&zU~2~TqB$#IwkrW z|Fe|(p(Qf|zJi(U7hM@eKb z4)m{K;fo`7MYIkd9WG>6L40`Oa9MbAM4N~n5tSqAM|6r{N>})`@Sc%FBj3Y9W<(Z? z(nnN`tQ5H>@(4WHEi{$|>2v!=EQ-)ZmWX?9vp z2qQX0o6s57#vlHa3OX6BoEYz9w78@=K~zOF39P&knAQ$@+S;NTA|;*7HSog|!tcUc zqF17P)Nkc^>`%O`t3}W0*qe!hc;n=vII#zQ>?-*CZqUFvqJyIDpby2szK)7+i$36u zSOq?u2=Y5xG(!}F!ZC*brPCoriEP5X)Q{7t?JMxMMQ~Qon>@jrg3|&q*{^O;!tUr# z&w$Nbp!!t9o0Q_Zss;XH3kd{Y@Z|-#iWUm?3%-QZDCq@)W?Vn-xd$o=I|`rw?%Kh$ z$J4tG5sc;jxgE-ZyYC1lTbFD98kl&IQ0dTFo_`HiVh5bp+t6d~5Do0B5r6iBkX;}U zj-o284J+0i_stBh&A$W_`Q0kAEADYeeh#IA8V==ay974{XYmxY=RWKS&RAJcU7$k~ zm&X0{k)PX(w?edJnL!hITg3%SLhD1Tz>zedmq~a|iiZ+H`d~B@p{4YR19?_}`mjOp zAspPk;EmwS;B?StXJAQiK`;faa8=fm>%q6dfmETXXmek|ybKEt36`ZUJxcF38GhzG zd%$+;g$vXZ%~+*6p%8qBE_^C`axOY?z1QU3?_J~_LjCcU85c`E^E^4Md|lkV-0fVo zslFb&8j>IK)%lTBsZ%KB2caw&;S@RC_HvG(JryNGK1Vue%qiOe+Zwp=*0#0Q@z%b! znzr8PWAK7e{;%x$%fvi7!Bv2C-BwiUzUP#FcpVcRI1&-TDJ z($>Lt%s$?}z}|~wtYvnILf1i4Pv#Y zV@~2d;~A=zLQJyVU=$e?`ZCOl>}jy*Uy;i(4Q);*LkVUuI`ww_1ATL5F&@_q)(z2@ z*MHUR&{fv^bspV)omO{^xicCj7=F+O!H9ooS8I#uUTI5dZ)iH{g4!(Y5v@qKSzAi~ zQukatS=$CAxq>cLyGq|s-<#b1N4iD2^ZLp9>Uybew4t=2i#|VI{CoO;@n(KtTH_AG zAe^)Yfa(bjt-xinSC~-*dboBD_Rrt#d3R zPzvs`B=dZ?<+G)s#b_2(fIgEyEh9%)gTCtI3ZqRn#CAkqhFn4`?E@GJK> zmp5NGE#PZa&Erfdrq9e4+ska5RMS7E>S&x|Os{xX8YdfT86!<@e2($PiKeEe?xsqn zNVCG!4o^+2>4z~EZ^!R*l#bV9Jb1dw_#7Q|Wz$kqXVZ7nzdR$KX$P5{Q_0M_L5fd* zbAtH=E|T%w8&!Fg715S1wDh%Xw)U}>MnhlQS_1v!zm_M~189QQSu3MK`DlHDN=Rm{ zXiu_-*O^4l-a=cLd94YRkhe@FFF-bfpo?f1go{7wvi1N9-4XGpNdWL&Kp0}RKp4Pa)c6si*Z@GWxtQ~Z% za}{=ZoKIc5Tsxfuz;=(I!M*Hw!aep7UG93vSYEltK7^!{+jyG1Drr5xZ|>x_=wiMGA2 z#b$nrB5nz@MAOWd%%{zhc}z!((2{A6M#rl#tIRd6wX7?wf3e2229rE*U1IBw+Ukw1 zFZ$aZjy;Zi_I&n*RIDkEV)Td0oG-zqHXc*d(I}6T$=-WM ziqTb1H*X2=EO?S=uZzCPgF|kZw}?N=Kf~9~ciVr#zY|yaGdy4Q@H&rU&T$SrdI;R- zBncE%g4KeOkT~=gd~XTx=zi=BOTeTeQA^)|m#!_SDQFjJ7rH38D>xQ96pFxF8!vE$ z>IlM6KyDWl6hwh&H5YV+AKT21IVRLTGz(tlP-rW=TxzHwyreDUfK@9Xkc3>p2mFk< zP$bO96>8N^q{PodjZmJfx*p&as=!Tfky~Js+rSi#237`YpzaU^vivRl6~PUM_|trk zKmz;VUQ_v`=*<51%|wgP&+7m)c} zSfg07XN912bNSi_;d$XK5Yj*3`ELtP3B~Zv^HB#?20xbzOw?vo(Mt5AR!<^Yei;sc zK*&R#RESQaEy(E{Dkly4f*bG!52&P4sk$~$v;5Y(q=8r6qbDfH)l~xxNH@BIf%HPP zLKQ+i;S?rne@|qDTIO+`X81mm z5c&Tui-Yi&uYB*pVp8x^-1KI{XKV$>GJCWhkK2UHW}7?S74B;1?%?iA`qxjYqc8Yb zUCd6n<2>rrfI4kMh{V~nE<`I27yY{y*3 z7uJ6@FtwDW|Molxr+Sl5^)|iT?itROS*qOFLwvDV$ zUFd};QWcf5#oKmRXW$pCW*up*XZ?;2UX4$2uVuRBxOE<@;}uI8TZnye2@~x*Tc=uo zqaz~gdh0T4j^(W-WHqv{?6dqtgF2s|@`6XnnKL|%M^(0|s79i#H*EcFU8%MLwp9A? z4(LuL_Dr&k`rAtJciH*tmf7ao8`+=m@v?mcuE}LE&+SQM>u%4oudp|EMmk$M#EwH` zTm6lvA=#Nux@JE|nDdciCk}zK&L567s3mJUh0ay3A>^%1b8d8XVy9S!)~~i}nR|{~ z<+|xIxL=cr73VJQDedV(M&oVz@$sHEtQQaP)XisNg2O$6J!-0lu`%B6-cO!ep2glj zK_(MHPTIqoAK`mFJm-8HeEUEwOZip45@0C@&^NX5wer2jYum|R-+vio{+<6{GTDCm zCZbPV4t_QvM6`r=D#ep|=z4^4I ziKQi8v?kO*<;*TqnB_D7zA(KrFQii2W;$u=U|MS$VybTvm^|#}D@}u$PPv1b0^f~C z@vi+c-ZAztRWwcI5o?U2_`I0u6KaNWrXnUQ{${Q5I9C7fhE(HD<7i_&;}ydmLnrEI zvEioPO07D~P@5E^U4}W#cmGGfhy)Lf?z8T+&ZNu3Wop#cVjgs^_M^6dE|YYo2HKOP zemvH$Bl$z3O(yLnR`XOnNZUj!Rqt2FY96TnP={0<)E?dw)VZp)sv2qqPR0L}iGOcX4 zY(H~Zs__5CvZ}J1vYs-d^q_RJbbxe@w2d@QdO5GKOhM|yxxC4;3bLKj#?o!l+R{=? zIlClnCYAEP($X_|KIvW(IJV~9B3)y_?uj;6}x7w)Q zu9h)NpqnPYW~44kw?WrX_e1x$PD)>}LElOL#IO}NLIJ~N<2-tTvc@&0_NJMpex^63 zQ>L!We9xxtR$4w;x?9>mY`ajmEVBLfMV}=*HIY=k+m3(04)@tNIZnbc zKXa5pbAQfx9@M_AbF!jQXW5qB)Pj8|OE(8B-DU~Psg?4A27|4wxm#!Gn=jqvFI zttGQTS+98nUXi!0w;RgI8opt08*{-)?Vc!VwmIIn)EaTr!vFcc_-=boct4VRmg!aS z@jc3%6~00KelWKO{byNC;sa&S33ep2@0|abZ@cfI|AT*%e=*6+7f?Spd zYt&-L(XV}jhe}}$Y7lG=|I|Hr08gC^3}OoEg*xoG=i&V>2A>BD!02s72{ja4aVSWl zCL|~OaS3|NQ=!(vabRp|f;p@yE#X67phdHx&f7^&-3D}J2g!@<2A1Z7>B|yHVbN7+ z#WbRe=otFJr5D2^d5y2;N9{713^_Xp)NDAu*XY7$;b-0~J_?tf2jVnetds;KJ*#l9F(KI*BOkZ^=&9#gZeE36i~_VGYANpnvEqo{K(yisW}L*kj2z$yDmL$KvZS#T(%oZQ^(00AD$U zN~^B8zi5K!u;h*8JsP`eaE=Oi<@cfou#*X55&6=^#P!98L8^i<(8Z_^dvkoof%j}e zTlp>YAykjrrMRGlU{&xO>g3(Qa&Rj%P5wLn7u3Y<{l&-%PK7~ffaYcLICsDe#TI9kU%P(MWWqKPm1II9fW}J0)-=)yNoX>?q@y z4ek+35??2NY8Pi~rytMI59fb;*4`QGigzXRl^i^h>1gV|!AfYHsm{;f;bUEcT?^^om{|aaVpW#NL*2_v(AIgo8+0_<+;1z09HH4&==idH=pZV zNPiS^h^db@I4aYJ#*<<2`(BmWf7m5XqeE$bVV^>6w350&Wxrvs%_c?RV$_ zvuri#W53gh^*{@<%C;HAXR0lORpBJq$tda*vCVG1OMP;f{(pq6y{)=6*;auor>Po>xrfFy(_IqwpBtB$MwliWdm77_qD+lI19}+S8NV1V z8j=heeIa9{ak!zfVXbk7@jt_RgBbt$YaCcr`1&e*)oMeUA<1NhMVe^5MIuLEV`F1W z@PqQE((G|NO}|V(O^r-Rrmg11;67JOZFyxcOt(#s%rDG?m>qEsf0xc8v<~L;GbDX1 zqXJAs=k$B6oKBsl!EJ5EA9NKz)pTR0Ed^X+8kKT6tI&FyjFP&xC|*&2kjf6W+Gv_~Fe_sb$VIm8sqMb? zwe_d%Am6Egi|7f?uARKnA-47O1JiBeY?JI0>@962ZC${0V(e<$7_zJefFgc|>ugQ0 z@B}{bpnW*s7250b??8JAru+@DTdBFmaxA9faXn+7Za?YR#0p@xA8|~AeLD#Q7R5@j z3|CrHM+!CCOzyX#oU>=(Lv=Z4Yrr&q%#=aY>h+zaVc*NJ zzjk-_fd_v^u45Tjg6qEHjAONPku#3(s`&ggyYXlH6MHMPsQ=ow+kWFvU2#c0vaci7 zz-}vp|7wc8A5(>twmtR@_V?B!)=FgU-mspr)`H)AW_@NYV0Bo^g7x(S$=kx28)}IG z2Me=~v9u#&wF*bO1Pp5?xL-Z%4ltlwJog>i)J9-qX(&~9(>rOcce(EiTHoUly{?}V=unrEyJ z-Z_}J>c&k}ZSxJS4XyPaT{VMJ|Am^ar#?on)Nas*>F#OoYxk3S_*s)*o18f}`MXZ(8R(zr=T_97)H_Io<Fifzie%4FqDMJd%o%&-IQ4~Uo`79-8Dva zUu{rxQ!_%dQQJ!UOM6W_k!nAdS<4S~D|D~*YxUpAG%9Q`=~H+Q8svr}hFOLe#?{8{ zrn&SN8;mziM>%?nO|Q)Vn5&qLruO7(OeF>253AR*lQklSOyVJ2TTkFJ1gu@PZ85gJ z^u|K?3#aX)?Ua2F%E$ut-OTcM#5(5)iiN+<@#;q(`3{J3Q&>7bRCcb7+^?URK z^;`A5NL4rkMtTR!7E*VeN+8#-JiNN-4k6b>0jTqe`|Z_TIy0Y2Q((_4Q9&q z&^*Rh{)@XRMQhiL(YDvxG+#AUw0@07eMz0E*{unx->C=UFJDAz=Nxrg^<;H3^$FDq zReg25`WW-0)pV2XRN<<_s@^Jva=EgZQmZJg%v5Yulvd19)>gJ*jrdP7N|7jkBYPp6 zD?2Z1D=Q&;C#@&Tq!WIbmm&+7dGq$>m6u&1(eqH=tGp3;W%KUj=F3}^dpxgSUW?q+ zoXfdgbBpBs%y#FT$Z4GuL;B?R?8DievI}M3%Nm>Q&RU%nku@p%YnCUoU*^fIdRZ?s zJ7t#6T9x@MvutL`%%vGWG6!e2%6yZtKFiK`-enBT)MZ@HsFkrO)19#*GdlA?MvaWJ znfEi+XGCN?&+M3aI-^xarOam;V>3eOCwN?Ep1VD>LgvZLdYLOToaw(Z`|y)@WfaT0 zl=(PwLS`^?O{O7hSytEVk69I%!+JAoN>(uYRCecVRaTjtr`ZFt!?OiB=d;J=6wYa% zQzplnvm$45PR*RRxwCW2<$lQ-nJ3KKk=rZxcy909DtR|@cjea4{ggK@?=yM%rKDf; zTIVTpH%XgH|B@z3FH46?pG*IgYNflRQrQw&g6x^Jj$9#2=J?f>UzeQ#Pv|E%%RX?d z!{quXl>aI(f^C#g$&_zZ^Hh1t^~x`*{h$No zR4M9M^}nids!!^<>Z9s$>Y5t8`n_hMCSH@KPSAeQ%+^F}x`3sGYtE_DGT)3*}4YIHg9hifiw=B1Umhewu6VoqVCZy&_4`K^~N?loyoGlvj|~kn3g7 zsP13f~Q+eI<@=Gt~yHWNGWPHvd)wY9FmG==$#|kzyU793aEGsJ8AT2K)!x>v5 zOOZ9^ye=T)K23g1epb;+F^?T|GxOIvkm-@2eyb|0{-L_1Ua2msd80nBS*5u^u3r)D z3(afYYF&L@KAl&8SHIEF%<$5%%FxbO3KXXv_}d6F1&f+Wn=|1!&zNfdkGah=*SA!# zBwDqW50;abY}V30tof{iZEbBAt(UBSq0^ec43r|a%4F^2qfYtDI-WZ57WH#wdxCu* zi1Ptv^IQXEzHD1h57QeCy(o&W%C>E20oIbkmreyf%-R^eLAG@}&u;|>`jIrglAzx% z%R9?U%OcBFIKjH+Y}0P@aC2po-&mcjexdQQA;+-5aFSVkCs^Zpz-4D?PiX(p70~U` zw9?Gf)Y5!VEm0+^->8yQ50nYa6S62CDJqgj=9Qn9Uy^kvk!+AmB2CZh%Na0n&&B0l z%GsAwD@V=BF*jS7{Wn>1j;sS&^|KyiF3hZ!*_m}glDR%(cV_F%Mj5x#*Jm0tre;{u zhiB$xSTojUoX)6_5tex_V?u^E{dX4K>x?!T8JTl4H)kei*3EjDc_nLT)|sptS=V#A z=Pb`Dms2ZGp1U%)a_((jfh+H9-X*TFWcgd!ZP{p91!|!(q#!$#XO;JrOO?O>mdWI{ zA7HKhm(04AntGZ}+8FI+%~VYronEU4%{DtGjLjY_*m z+g>B%UMr@#uU?@3Ozky-*{+*Z#Z=+s$P`fBR_;~RVWMj*jjzO>xaIZ3%51R;zouaZHWK z&-upvxgjdu%1fvZAs3}j_HoGOpcsjo?BiZS1!V#za3F*Y+6HmQtj(UG(?N{nOmW%Pd-RQgK# zPr5Z^h(_v7I<;=8?zwJ(uAffG=a+R>?Rl*q-O3s5H0>eXG+k}2P+MC2M)Og#R?}Q# zQP<*HY^`~&&ep8gR3{~}nI>0VTJug_S94oEPhD5NSe>H&o#m}i9Z|K=80cE$D!ckA zi2Z%lCCvcME_Gk^RrMHkLH3TT%rX0^Ij7m6Nz&}o4$*F9pZTagt=-2o-9*l5hW3zl zzOJ6GoIXc4n{?E(+&drX9Zu_~=?m-ax(LH3{YL$KeL=K3GYpLk;|*mDDMllc?ydSI z#_7fhhI)o>RAt4CTC~PzjNc6p3=S}(kH%d_51&8eO4(@WU|0sybeNph48s<~Izv;# zTXM!@jL!{q4SD+UhN^~pG9h+s%@?9rhCf0Jx=>VJ6-!rw~N1a z2|UPgZC9;K(^Y5FDzvM#i*&Vg<#k`R^?6^RE1_GDUZ^)2v9H*j-|1HAqIGYz<8|RW zjdrtkB|A!6bVe@Cc8yp2kM<=ql*6QU#+l~<#$TqY~2cq{KqpV>!Yk(W_^ zRJ>Gl;%7HeuBH0?q}oI++EX=HT}qv!-mIR%>q{Z`y^{8arjIUJmtU8q9jdRQZ%J?5 z)gUyCHPkd%Q9i9Qwgwf9FxEHOjgO2wjH{U0HQHRp+!m}+Zaz+`*75(<^~EeQbV|=G zH_0(uW$jMVSu%;p$<~J4XA{{o|D@-VdLz{WXfH! z-6tc+Y8}n&t0a)qL=cvnXsewdOrPvmQK9{`RR>S&PRd+ox~J3jt@hH6G`p5$HZzI0 zOF?#Sz|IaKz3m3BFUjQI?EUS*Zy09 zclc*={Gaf}-3?sFZ8sV&CL%D7IcbT3lt2RWZQ{`74DgTgr!f0ah;OD3U#rLGJx~xN z@0J2^J|oDbIb)vJMPYFcw^pA=6h{RxTQY5ZZXRza|EbdZ)P84fVua!7PWq` zo(7|@WwSE(?mhKgZR)?goyxAS=h*YaWd?jP`et-(@*AX(|)v?c60 zASp$t2D{l>a<#W68~VC!zU>mqux?=fBR~a<+OklcHYTrmFR7*}6(R=8b*|vq|Q%+W*0uT{o7@;$FHqT~obN*A$+1sjjf@2mRO;`t_r9OO4p` ziqo4tR!vc@R98@URXLQg%oj*feNy&OLd*DyC4ARa3m8zi>#u%T~Z9 zZ)=<~z+W8sg~?=8M}{_*2`(^6ZcJx`5Io0-}wb?>)}sYO#~etZ430{Mic zzMcHq<=cN>AABwGbt8kYAAU{ws{7LGi}h=dubsXw_@ew;oQdm|zRdgj0hez zwO3!ue!cR=_xZ>-&DW@JqrQ$$z5A_9>bP&>)Y;!gr+r9GO|6xxNo$(6KP@M<~Z zOCOg$BqK9@VY=;Cj|^A(9F9_vjEwY*^r`8|8SB!gX9UxGr0ai;&Jbp7P51sP$k+O$ z$E80>Z<>B3y-E7XbV2&V^v>zE(qI0nospA1H@$ZH;f#_Q(HV!*=Vav1xCKHnJtHh* zZf2Rx0U74>r~-0-v(vKjvJPeq24$(A{a@DU>@nE` z_?ersi)Yu(O3Qqo-86eIk6MsjH+yzYY|bL4T+Pc7<~+>q$2GGCgl0(g$D9c{&2yzW z!rUV{A9Ii9F3T;O`#Nu4UWkd|UWs#^O%u*#Cw?%dUtwcZBefelQ-#xOa ziee;9Uy;vLc;w?0RTVb*CV6#w-=&H&inY`d{S_v89pz_5IptSHree9`EEvEPrAzTc zHAnSE^|xw|dV=~j*oHxUPQ6r9RdZ5PTC-W(j8y9tnkG66h=WVBTvt;!1zchwwc!G7 z719;2Yi59s57R_wYHIbG_L@v}18t^8tzM`et#PYgs+X$osXM8=YgAOJT~+a#wd(xp z)vAT+g6avXXw?B#B~=ShiQURF%B89Z)ly|~Wr!N6r_!O&DZ9`m^`ci=rKqL6%aoKU zauc=*NHH?bEvoUV5@6`r>ecEkU^t&tlfdQ(&w|v}FTr~rsRpZJG`E=2vr(0yUZ?KJW8&30sx*4j z`t*W5)naui)fZ(0^;gvkj&p6zd-YrO3iVx*yX!Tu%Y|v>FUXv zhMM14Qa6oBt>t{^)&Ht(;6OFV2`#IAMtbFWbvurCtmcY(yQaFPl;)ATz9tn6a-Swa zb5=bO)M%Ziisq+wl6Ix0fu^@MU1QN)(+IWKG!wPa+7?=sW(#%gSZ!VHIbB!XLGY!M zx{m+Tv$WFNbRYCPsh3yjS}>t*pgvxI+0fO{(O}d!0974<7JnptOuBKm@sn|yv6A_d zX(apSC^YSh%zv1#TQiLanSdjhJV_N zyJ0*0eiyhRJ-q8PJgTkW^rtez^pbP2a~yNTz0P!W-lIU*C8+A!xk@k}zzs(~&t2Vp zkh;z0mb$CDy{-f9DeebM%lLsRyMw#5JJOv;P1oAJ5RASJ8oO$!?K0g8cUyOg`?32J zI`OL@{Jq>6{O>#dxEbzt?xFmi-e?z^Gda~yer{2>)g?nG(1nQ~16|3kf^HK`m(kf4 zZ|q)PjoAIhbqycw6LfwiG&wz8^>}@sTr%etr-^Ll0WP8I5-Rj2%wZjj?{=oEnd=}r zf-0`3IDk7iYrvY-=P&O;x@H_&hycf6DjCDGP-L%zHQ0)VK*t>QHGI5`9%3-uz*M+_ z_wd#Y>1I#BX@6u!>|*>HHOUj{f_9=f(=$x=)9B^@CIR}0{h4Dm%K0jeT-dxOP6a=s zh*QDL&$GM^1$AH+(`MrCeJKFOOt@kk|bxibl(F19`m%TA42pzt3 zUm4Q89t0i*+L3d3ki6_}_yfm>T8E-Sq2N^9c;84t(uB038+hCP`aN?7uX-o^eF?&J z6n;kqC4}DuE6C|fA&o1Zbf$E4e#xR#;Z$KaQkO0ZSEAJk3AMtT!aKr5oN~kP-q#ZP z1+#^NgrCq6Mhidlj7@^s_?UK}<(&{}NTSIrYUbfVU+@`|9Q*T%M5IH61x0~(er@0m z{_*4fm_P;@ealGYyT}}pAtcSUL@7BQKe)xS$k!SF@+v$;Z%|96dveiZn>=4U0@$Lv zp5^39-*O+oPh@oOaJNCFp`w5qspGr=CY-QJ?Xw@ig&-+#~qenOyH>kuxQn>YF}wX3>C+!jAr5iZ z!9m`Mx4P~>QPSLS&2lXw16$;N>(Z0Sng>5-kU7pWX)?Lm02inFm_(22cUeaCX@rX9U|2>LMXr?QK_V*J$^&0Ts zulY(pd_i|$uo}YBCgXCgftI2S_vRw5@}YE5wfLEX_}UGayr1ynXB_L8(R>H(%YFMW z?qW6lSth!Nq4?Bilbt>8_uNE&?-d-;Z&0;)$W&R!z4I8o+9LN@cSrmgCXb2~l+&Km z-fj5nmv|1MUH;|GL7(=-_X!v8AiTfJeN%lEd=aEgocI0VZNYqy-`cff-Vwe5z6NwG zb1BP`BWd3{gr>Iyvp9xq4!l2R5#Hg)>eI04rMRRvQ@{2;sjMq-eNM&b*Qtnio zR1{HMme&L|n5KwUs1?iDB?~L+DsIb{C{h${+^igJXLXaCpQ>pFKs|G3f%8G z+*mjAs;}s}>F((!>+0#%y2BtoK@gdI#)l{&Qw$4?-Hm$;(+$tTcUFTItYG&sKAqLr)!Yd_vp!8F=j*?bnRZ;N?>xv|M(Y-A3@ zF?NsdXPIoKccyw~sp+P9CXe21>SZZy31^o05pae^^l4`;Bbm6g-jW7NSJ90X%!X|$nP7x6)j+4=K|ISeLO#U02vx*hbJ&kcxNvIk}Uigym6IP7|&ZE+S=)@JB(6 zuoKx!vG`+UWF77jZYNc5JWiDVNW)wsTuOduYjRFIi3W&@Fux~4lqXEVpD}~%lT&z| z7n6#31E0!Bav4{NHjB3NoN$pv7%M6!x=RMjHT=2#$xTcl9c-|0pm3pRq^JzpauH+> z&L9`_KK^Qw=0rMD`_>94kS-Txp+cmnY-GlA7cyNo@NZ|lw>9v#&*IG7K@00Z zHQ$Ep*kQufLOWS+DSVvI@jpg_%xY3&bIC@ull!!ve5Z~~Q2!!%$8=enL=+xQ*1v=) zfF4{u9l~W1ZzIY_R*u{gIg4pK#z;q`AgWRHu;}zCebmjEOeVSYVM?7cW@yZzn3XXH zVwS`{h@BL>F*Yf#aopw#B^SMRfyo8D2`PzX6M7{KOx%>HPiU4nF>!X{ z=)@g~=Mr-g&m`q0txdd~I52r@azs+Uq+LnRlj4(FB+X2El$4d!AX%PNGP!^9mE_}z z^2Et}{3B^eQmy1g$;XnuC8Z_BBzH-kmwYtIn6xqZOLBvx<$Tm64@x?eR6luC^5&!$ zNhgx@N&A!DB)v=wC90E}Cr@W0c(0_LNzD@fPTZELPh6gGHDP-~YJw}FKw__ixl9dj zkXRrgC1F6~^2GKDOA^jA4?HTNX2P!mkqMs)h!XDQf1W=gp;W@N{9p2W^VcfyD*uoC z>G?JJ8|Pn@{}xAMPrlFjF2{>G64UaZj{iimz9e7%_?GeMaS`z!V;jb0$GT%r$9{^P z6>~DCeeA5*G0a=ciZR9d-}4%~ zMgA2zJMv6qa%4)RG9o^*UgXlq(GhDSQX*U9*KZrqIC5a*TXM5I@NphK{%sLgBC168 ziQFCWIN~I8m~;`&2u-9u@?ga6h_B>ncZnDo(TvQ1!MyJzhr3V2gowG}OTz1rHJm@Z zP53@?v;PdA8=f5=2p`Qn$*W{Y=a4*n6=bnQ^u`u3>Di*#2=#xi>Gy{udV)zcua#8TRettH%$G zzY_N=ZX5aZKjTF4UE_Dew`U^STypFi#xIC372k%8`!Dfl$gbZScRH?b+{U;T@qfh+ z1dQ^XY~4S!nPthl~>uVGxPIAOdn{v7jF%f;7-{~9NYD;C!-?p>TS z?gp=FY23xQRvr^W4yJNtheT?Kg5&eN`+Uvih6?9R;2T5hxNyy{$-EiS9o8b)&nYz^1c&`9cm zYLBL+X0_^)YLa@ano?I&C#Z&~?y9m>ja2cfNvgT3t`Jkx0CD;IDW@qjA*N=!@|^O6 z;-_M`a*47!L>`}{+^=k?=&0DDIIj3CSHVb#Q?yg`QA|@TR;ZMb%Kve1wo)zx?$^)q z*O1apF;ekf{!1PW{E?l2o&LD|v3!B-gzS@CsrV&R%6rS_$m8T=)de#zB8ig$VRNLlD`I5JL6{;$oD91VUVlrSf=LoNT3Ro9q+BPwyd{Ci@#=lO2To zZGq$LbVx=>f$WJaJ!DTv81&kT5QnS*#N*f~yDY0Mt0mhbKP;aQT)~^<>2kB2lZPo{ z6b%$ZAreP|qJwg<^1I@#VguB=iL$k_BlzPKaN5mQomXY3g4BmqFI5fHQECQwHoL1A zs(YwLs}87ds3X+%;ong8C$&yfuJQom=y~;g)m7Cr^*wb2^rcw!YV`wEg{r%1n(7M7 z?fxp6Y6WodE>d1oMyaN#dH{c(68dEgWr}jXa*^_n5?4J_lq>EjOO(^0f2))hr3_e2 zhbU$#(iPhkK?;Q;P!X=s$Z2`G98)}(Rm$$bT2d9F@c%EZ2;%au1Adl{^6~P2G7YeJ zmdJa`lHija##*qvrEIROHN+FK$ds~OvK*OL76Bvmfb2`iH}IB8wiDVQ3wW;@$f99B zX%@0wb`#oTSx5)qlItiNBYO_3*;V+r2Y4;BWM5=*86~?a%az5!9KeCEcLOt49*p|k z5bs|Z@+-KGtfs7g$kdQrS-I?XNMXoWm<#1F6Fdg4fNJtq@}{z;aE*o7Co^SN<+<`? zh@<{lW|x;j+!Pwt$}e)QVvqc~{GQ^g;tKGfL@L`U^~z?-hsvkQuF4t8qwpz8HB$9a z`B&LUT}Ay*sZ%AW7pQ-#-l~qN->9dl52%l;v($Gr?=(F$Yc!j*YqVLK5{(R@kP__= zZF6KGG89>e+(I58F0>(9j>us(>8-Sl^Le|2wl9{oMwZZYfY z>Wi=l{HXqiz8Ag}FT)z*Uc40^hS$P7;6v~acs0Wfyd1x3_-D9;f5R6WvJLeO^9?Hu z8HSs<*FYN<8j=jV3=i>AdDH2X!H%|6w3!PddP&A!<7 z#`e@!+kOP@H?>c+kFaIg>e`Rk&)Br~FSbs0&c4L{+*Z}Da3ndZ*eBcX*;&V8Tbb<& zTov~5_FZ7f^xC7}9$7+Yye%v1K$aSo;`#JtPQpq3oVU9VDAC9h$ zBaSAH^^O8ZL-L5--|^egjEr{dakL|sk_nD1M`LmW8A;9{JCiraw&Vr!fWt)gA$WNN1jNC8>61lMS3satvABdCaLL=aA<} zvvV3*L8g$;Nya(LdCSq09OtZb7CDm0Sm$Nuc#!d%obHTqUU7aRyE&IS&8{0{8|MM1 z%GI1)MD}r>a;A_aAj#jE;QZ>0b7neO*DhC#^Pcmcv#TrAxdlqLb}5~6oo$>~o$Z~s zoXg2TrxU)91D#i#7FP$5k?hKK6*;@RemaM{w!8H1k5JW~;qz@{-W_AS$qZNxleK6vBd+Z0yL ztYVI{wYbAfGj<01n)R@I*{4hw_6ggS+sOQ3QrSD~Z)Prgl5NR#h0hyUC%1?-at7`K zSB1OAWwRr>5^gFV!Y$w)a&zFil$*-Q_0&u zODjQEx>#Rw3;zjygfgLxAQv|YO~r|#Qo19I5l2e2?}s>FDwf9kc8S#`PC6=m5|>Nq zVlyc~IxSrn`$!?aGd^6pFS&fTeMRCjDaQBMSHqX(i;^BoZ>5307vgB?qqNbtP^>M@ zk*fGsh;5{WqDfjPi9W4#OWGi{@wE|ui*KbxzAa+3R4E>o-ivFcUXtDSpETUpNO~l7 z@KyN6OOd{zJ|a{h?U%Bot-je(9bb1TPdW%16D7`P^F4v@GT%mD%g|D3ov*Kt3_UB& z@?Dks`AVcsz7lbRbU~W#6Qyg?LusUMq-d2Qr2@$-J&~q}gj6Qpk!pw^MV)j=GK$AV zTskMU6U#&j>TzC7kmibRsZ=~ARTuAz7U{NhPV6hS0^;p-vAI-7d?Jn)L@^fp`d!#9 zUgEn7mBM228sAfxAVi89AxEeN?bTT*7EHn};hS(=Ea6WI=Y&*o7*7dF!f)X|-(Pse zCkQfOqtK9l&tK;|3*Wi<{A^wlCIi6V!e8cRaDn_1&dOhB$8!5wo_ov=><)G+8^qOy?~mDeTrM+~?ZNr+ zJlIyij^=)_d%64UFwV;raO+u)({bCm0B#RAnIrhlY!NGR7r8NPDch9$%!%Aij_20F z|Bd)vEXBR$2J`?S;SG%8F5+Ebkmwb2#XtNE;h~T$o(8?Cf=qnR z&lfEGGmsfA4B$WU1^hJO65mM>gcrhaJ`~3KATb^0hb}yTLizK;T)qkPwQs^9t_!~r z`qVovm2V05E#lhqyC7{4Zx&*>uUvhQ)q=aib>_49wVcFv=Ck>eTpdUY;IG5HR0X^; zpVtf3*tg)_7u-si6L-Lz=i*LqXV|{n7|zUJWBYKMxW@ed=Dt?o)#=d3j<9XHV{8KV z4chQH%$Z6y9m*f(ny`0au1#ZY>IN z3)tC=lfA(7W2-}78p0~LrpyVLpNFz$b|LEq$)W6S_J?;MbDF8n-uJd8Jb-L z{rWqz66WnO>@l$AAG04c_T(a=Z!6g?>}aNvna_M>w!w7xpSLB0vAfvX%pGPh`;M){ z++=<;%h}b8nvG>LV78ynhB0ShtW;r}d!KoOnB7cs?@jM#?@VSeQ_i5Unk<0%zYUC| z*(}Y?WCGY+@QI1N#BKq*eC%~r1KO@JUD;M3?-tvI%VdVLgTSKKOfnk)D@=d(2m2nz zu$7y|z4H!ZwlX!?2FyE#0MA4*cbSo3%NEwobzqJ&23S)zLe4L+#>BD@Suc$FD=_-6 zF#}kFTgClv%=~3XasAjFR^aw?X|NJe+(vFIw5yEU!D;#3d?~w)%i)&r4Y_w*e;7~G zIWP46m(UZ&Lz~~=szEWpUWr1O0*IB(;B{|P?HZ4?(w4p6Tg8E6K)EFVYNC3 z{cAVhUHHl`6?zNh!W_P-a8l?G^WPwt)AXVe)}z@%l(>^>axC2(LETNCMPwX$v5*opXS}M`v3-Kg;zaY+( z4v8_+cBz#wO|(Jzd}$oa>n`b&R8)KH%+E*xT^EssF66L$;+|^XeV@ct z(r;01YRieU}RkYc0~ zX`B=W`nvdh;yJMp*2H2lQCce1g6j}z8myDcz?M!@xztknBW-|s9+3L`E{GkadeUp@ zu{d3tAbEVh#nsXSah}9T+0t&YgH%(}`09zDA*Gt{j5t6-qz_R4@zN}jgU%7p#~wrSHNfXzwKHsxS;%e!MhHWT9R1#T(*OX@k&C{2=a=+~RfdEwsTt znA5h3VN#-~kdnm!X))+sDR5$*kS_KU3}UdjS!^i05`x5=qFFd0OcQ+Kd10bhTlgY` zi2Fq!%&CaDTihtbh#TNJr!~yOTZOLTF(FXQE!ardUtmnCWy3k1YDLmxI376q{=rrF!xGu~PAM;a%H9`l`Bs>tzP<|)W zsX5HPU-*N99-iAA!Uw^~KZU1&F!3XQ4xZEY3ofvB1K&naftEqM0mjT0p()Jfc34;1 z!CYJjVmmVPlj2ch~3H+atHX$Y-4T)$ME~v z8e9=OiF?g1;ikeIXovN4AKQqF<%(ck9>S$^!Tb%jIjk4^U`-go)n)&&5!_AgB0P83 zfVDXSo}lNlVcam7vEDO%+0ATic%J^nR7L%h zV0j)jhJHw|@&?goLE=QOi#|?Y1^XlDZ**t+7u}Qg^B$lQ=mB&oJ(g0_m%$ft@H_M^ zHH#+b6Yx9qBHe;Uz5C#|=6-r0ThJJ6N% zC`zDn>4n}j%1r;Gw$KLp8hxJ{M^B|2dmB(Xx|}*pbJQoQEnPy_qjISrdLE6@2k0En zED8ndPI(4UmDCyTUUEx_pU7|YB=R8ZPB~&>5z|)LcNY$c^ z)N!g8^^XdtGN~h;KGa?+g`VSSNNu48({gG%^_~K&DGi-Qx#?TfJo=mG7L@NpccGMY z1hiiy^_`-iKXjl%={#x@9YpV?^E`X0r_@rYS53MTZT3ci&D-dP-VyM95@n@}slD_e z_*H(DN~6Q6d(>xYI~_osqh?bQ{n|5~y5Z?hHKcA)Up-SO6ZMEn@ci-ksAtp+Pb4)4 zeyh)*TF?`yI<%6yNnP;_r#esu`k5yQ%G>A#p5|05DwxjnjHc#Mt>~|w%~Sw&f~p2u z(mXDTr*G2pJQ8S|Mep%+r*3*CQ!hOWs6+5M-?M`X_vCm~u*dM(y~HC>h15DvG-ZMB zMPQc)u7{wl8bB-C=`-|VPXyHzJfG;HC?57C#(Jt!{?sn2EmcNsgguW$@JWA~ zxaYQqq|Q){Jz>-<&l+mJr!_U+(}h|He-6(JPqt?+b;~`}Q|b9%-W#6xo?{f^Iqx~_ z?&ewUZtWTFHh4yZo-uB-r>)1I3W0qTKiCDi=$`NC=h0Ewo<&r9&s)zE=mTchXGwrM z&U82OT=q<&s<_{|AGs%Zp1Nmw^4)7aFWgCX}S@%JAAI}YU ztmm40E9h6$m;BA=Lx2cyX$#K&m~V|u!iuQ^fYkacmL<^=Sg>` zd2-wXJrmqsPY-tp*f`3Q==$h#x{KUzTpQdM-F-brz!uv5#GMW{gu9=+FT00(-nrJg zkHKEkG`AM~6XJR6n&)oq3UF7tPP&J=F!vAl49`thU-vn8e@{Jki9699Jo+^}=C)FX3maKCbo_q=g+a}RdEcUN~_Mvy6)!g5AN-**6t^+6nCWiv%A1G8UA1FndmaQ zw?X;ME(xwRJgEDydj;&VslkHLE<5N`c{aHs+>^lHQLfjnc$eVLg0@`dY6iYr?sm8e zT*KT|JWX6mx6XamJ<(O$eHT)K!Gk}Xr(7MOU)^(#aM|4Z+)CFy*9e!)?RI6lzBu=} z{M~8p%g&~*L#`ff6#l>AN^+;VaQ8*9cZ^Hn9^m@!iiI``bgy==bccJMx|X_IxQpF$ zT%7xnYqoo@tFwC@ozQx&i!O`njO((qu`3Sz)ZOXlN_WM%yE;Q$D%Wn;b!Vc>;X3K62X)SN zj(62?J#|IGZt5YBf0c}J#yj6S{hV8!Q=KBnI_-*do_8u-CtMrIu1=k6rR%t}zN^GJ z$;G?!Tng7RS98}Z7wX#L$^ac-os(TI&^Fqsab0qDbje(sT$7z)uJ6t*uIWy~)yehK z)!M0rvO8S$K~DqNPrd4lakX)Nb5;Ypm%;w+E9WT4>u?=&M!TvzKRD~cy#vk!*DrD} zd|T|gO^$^<*J#&yurDog^IfI?ooL9-&&R*eN*+oVUm{XFca#XLsj2XEN*(KY@B~fE>e}l&ioon(PEx zn~|T$4rDR;mYnL`4g15r{y*&lX@q^|GRGRSweyLyk@LD!=3MS92K!P$j>WmnDTn=R z2GR$S-^n#(duJl)a_%5&JCBjPvjuq{{=e@WK$6a9M+ z6qMQxJ~<2f)*s0s&TP0YCoyLjd6{fLo+OjNrV7VYaxLt0&u}2*UXpgsauDPQ*ekyV zd*xNhi{xOklq_%zBpW%;Ir}?`!P?*CXorlfOa4bjkyps}k%oqrtrNQS&aE^$zhegN{{B9A&eqzm${cUZ^(=SJ9TKTI}<@*5o@`O?vcbb{R{ z98s`4k2>c$^yEUYHNo)r&wPcWEGnCdj$2&06 z1N;5c9ab{Z@zk-~(TJ=Ad;hWE{{fEgjyq6~eES$jzN0PK$5HNxf|Lu6u4F&QH%Dvo zA$iw6$+5{%i=1G$JDNHw9IZflp?!~|7u@$bZaC^Wg30doQu`=JJ#wPGHk2Jep0P(c z7CUJ2AZW>l@>z~)5I}jgBMod^W3TC0;)o*!c$cvf-c<;WyN-5_KynJ$H^EVt{N&hA zZnAf9bcDPQ?9&|c96Y(m-W=@u}{EVXMLE_grF6!JZ@jkCA1SJ>y59Kq23NA2w$g#D^r?bzqI0Qa)&`!|+n3s6?WgP`9ItF?_B*zY_LsJG_B5NyzTFmTzi;hn zv)ixOaoc&@IC!Tu(iUtVVEbUZXN$M*wzjwB+UD3h+X^AiYWo|I8)V;YH`@=}m)q*w zo%Uz;>b5JkrtmJyV%u*kw=K0-v%R)$1i4>rBkVHU0h`Z$-+t8E!8X$N-KMr(x21rN zcDB#9AJ!|jiMD3;dhlM&33Yp7O|kX0DeS4X65Dp0V1Htp4*ol9`)Au=*Me*>=;Cd~ zAUn(+YAdjX*>Y@`p)FFa;kL=P>h>Sj4K}%bl|2yT{RTTs_H*_R)&yIrZL)o_wGGr` zpuNaC$2P)-+v9B-dwZ~Fr0tK*-?qv&3`*X$#zLNC`&Db2EzL%N?mISz?V!zPyK7r% zb=fA_%=UDU=Wjb>OSPi5_SRzSHfw#`RO=V(H|r=HX}w_egZG86EW@m#Z3m>=Z4IrN z@ILWBTe_vS)nWb5`i@AooUk;sMp`aeK3bMq2U@gNjb*E4yM?xXA%V zERC!ii3XNC#5Rl3a>x>4xo+8SX>WZ-EV0b72-Z0SW?5pXYJEziS*Ba8){mA6*1nc+ zmfx1e*8M~?OEb$W%OFB-iL<=6xItHfMQL4Q3AH{Vx?B9Mv#gCR*^qM3no4Lb4MA&7 z%Sq621!PB9r$bsN>pAOgqOm0v-lZxm>n(cAIg8q|!7`RmS#A;IEk))zL=#J%WeVZ6 z+_Fr!o&eirmcy13u&*_dLp&v>TEd8JP?HerUP823EUPU6md%!2qPHcV=x=#%oYPZ(ELx~_9pVfTXUVbLF^?uPiGG&O1W+Xr zV=M|{Cb7gEOdJF`cJqGoS)z?4-CU2zCR$jA5}$~>L`O?~@UoTIMWEoV*2HZBv+l4S zCYoA$TRvIxh&h&5&>o{Ln3V;cZp(AaJ@Yi;1u@8C0^9c!(Uy8d2C;%5EH-$X+#Brc z3h$N8#8IM%7-v}p^@_4&Sw0cdE!8alSu&vws#?xlMp#tV-^6V2MznPc(Hi`E*7A^8 z1U^W&+_ChuT8O|0CKQ&l=5S&H z5nvf>MhP=nEXq9m3OC~=V(1NRn#k6M_s%oD)Y@n#LN+gyuqns1u_ znU@f4iQmLxc;DZR_&}tZZ<)ss|A>5$vl`lDv$+!y4t>oBvR|2p5tE5pmORr!$kmhJ z%@56&&D{vne8k+#{KfpyG})YH4k7lNn-I55?ck)rU81G=K1e=5Oo8(S_uxE1g!zHF zi}|g&run*gpt-;-fQ@?dZnGb>f!(~-{KhGq)rn&6#kXVFZz9nr>bI+KlF@@XtkzGilBFrk3UprU~Xx zrhev);D@888s?qmF2p@ZPXt*@OrhqdrcUPHrcvherncq?^IdbC>Ah*R87JDCa!hs2 z*UYs|uT29?C8mF-xn`Rw-6VrrRv3qyewwGa1KBta-7~W_oFgH4iZTGOaXKGY6RFnA2~X!D2jaWK1iKqRC|3Y5Zc0H7zn2j8#o1OudXZjBQOBrUc_%<4of}V~H`^ zBs2DcduvT^4E>D_P4i4WjJJ&^jnz%z#zV#=<1b^8ah$1@F%QaJH|dSbj9REc2l&3y zXf&-c)-z2rhM2m-_ZZ`ANZDpuYfu|M8b_D{jAM;y#tX)F#yiG5Ltpq*%VdV~QKknb zxp9thD`@Qj_B=7PG5#>jFb;!rHsg%E>4KpPSRHG6U`Q|;AXiP}H)C7l8{;eEOp|Cl z4Dt$%PYolC*Ni<(O^uI?8Q_mt;}4_4I18j|O&<(BjJFKEjZye>{F@=w*vat1unp?J z%@A(vV7PAhXc%uS!226o8qXSo4U-JB4FSf__)Iu4Yc*Ck+=TP8H4SqOLB>h=7yJm6 z>5AWjPmSPwZ6CuYyoI4Teggl4CF457V1ooFW^IO(hUIYf)?!#`up71-p5dJhAMi1T z3M>v+8&(^BV*TNK?leO?+~05ruVTOrrws{s1>PO^GpxrLD8JnhjBkN+ycRfzTY|5^ zCBp{ztH37Vg;1_Kz7zk5rx^;cf%sg|+T2iNxCH*ZV;Ep$3`Y!-@rtp&;WBvZgYk-? zjS-0xz%Z0Ka1W(YRs8-^Rd;-d|T;KjH2dc$2qlra@fGEFp~#)Ag8u^b;~ zC^dA4`d>Gs8^Vme!FNp!S%w<$*Vs^Ca2swJcEcIt>d+^6!$v4I&o~da8oC;a!3(zy zv%p^`4ULSwAz!F*jd35;{Gfq1`WZ$W2EkdW061Iq79RlTs*W2j<1Cy?{$;3(pT-fx z4ns@0zQSV+2k_>GZ}?clBJhY4&K*Y?4jb~ZSo|xV2!7rHXSa6ZBt8{RJZIrEz?XY4 z3qA|3VbBHzco%~XPlEI;ye)nScjAlj|Jf(u>A1(R587ZD_^_4XJWd&A;$`>(TyA&@ z->=|K1A{NY2jVaAX4rLXF)kW*V>Y}S{uFPHAHcsuxfvkg16&i}eDn;=jGx2_!!S5w zjpOt2jhG1^3l_G(f8brQmslG-7ss(3(DEPfXe=8$44)#gvv87{!gpZx@%va;{3$jB z|EeF1VfZG!2Fa8kgfxo~y;_)DF6P!gmgL&}d;QL9~KWqfp z&=k7_5-o7{doq3k)8TFKyLe;l2I#(qyRkJ`1L#4Qv8K2m-XC9uY4C#>iAUlG@wRY= zygiTZv3!&p2MB#!}$+GB6A6`(I&Z^z29$#}kgI`$pwhX;e) zf7o2SC-xNUjXlDyfQ*m&#n?HlCH_}G4!ecb!f)xLv0vB}+={Kn0(O1R( z!+PRv_1E=xuta>Sz6#b6dx2HKu7RAF*hnl8|Dj)iT>xE)aI$|EAxI z{nSszR_b|dj6PR?THg_Ss!N1Z@vATlPNIL*kHsANE&2p~p8k}+HJqEjsAsUp*l@iT z>j1L*>jSWV`UBW={YcCM8dAXzJa!Sh&==pVcVKNGZKu8&oL+b0bM;E>Dah-KY4JSJ zIS}LUQr&93AGQT+s=uY*qmRO_f&Om#n|c9i@6sR9CqQkF>uX@$vG>>={QzvD-inpz zmt$@9uk|gl0&JN6ojwTL3h9OVZF&*gqi>D{V;eCCwD}lvId-SqE4zX#i{uaCv+4H%1U!+z=}=&yp#7=4lcvwjTL zKz~%9t#5%f0b2`o?ewelHf*I%(l68_Sb;8HABH{0f*>_j|5e{Ye@_2Q*9*??&&6)( zqV!YsA=q8r5IE6~V_)F9N7q7+=r`++>#BlZy%?@v24^b!U>dki)oZY7V3%7z13XX@ zi_ZMIw&q@N3I$?A6Nh66U?AlNLxoeJGd_*`8drN5zHs?&ike=J8=Q$Jo` ztXD(dq4l%$ar&qFOkHRFMDS4+-A<4+Rlizi(TD2i=>G$sy#$LcfV@BGB9PANOVJH* z(lkWB7DaXQbXI-1?v*Y`w-D|{=xw^yy3e|P`nPBwIFUM3Uqg3Rw@WAJ7wRZ|5!zR` zR>$a9p>ohMLcam6t4r0P`j6;DT{qo7K!ND>Cde@ys23Zf=g>G^w$6htLR*5Q3up__ zF;%x2u90x>Gde_92YrYR(*^3+p-prX!QTC7M_oDCHWXx5MbDsXbz%ApG(k67SE9R& zHr9D{J9IVCi|7FKG`bAc>mDE?S_jfPpqZ#uH(1vT%G^L}=|-V4T^jlx-2tgQx*FY! zinsJg5Dy?(%FtT~i6-eD=o+CH(2igmfzCoX z&~z7-bWU^xT3feQ7mlt&!$IavL_!Cn-_b#6F`zz zdyF(if1_h`FOUZ4Ewq)c7@3I9fm-wfUj?9DLAzXc0jY-`Lv_0UklN@x)U5jkNTU1D zw}zl^(eCJL^f*fBx*>;=aC8;=3Upsb8$v&tjxGkDjDwYK^fT73w(S8@q(9| z=u(mI$Q1M$dI7urq^g8(h2>9v_TDE%Ry+jVW^*uhV$Gsx(l6y z1cAPv=xL+|nuRn$pCCQaiAW)Mst?%b25(`y5@>S*#=}aa1S|!uNPX0bEI}x!Sv6!m z(g$j4L8c+qpkJ&&{Lvz$IeHnXf}Ybx0P@O)I-oViKwqzm>_uv!CqP#lbRXCp2rWq? zr;)~B>vE(V`HFVZMI&F3&&W7*2NH;0LA219c=Wrr2lSR|XdJTp|E+zd?E>e~A0QUc zZA8{UYo?$hw7<1oq2IjIwnKV=yvE2bcKD8o93FAs2}()>dkhwZ+=)P=|{er}i4Ol`9I{f zwignL97X18pJ@%qAS6dqPn)f6jC9w2*7gFdO*idLZ7ASyWjDY)G|jXpwM~!? zn$wzA+GE;C%{k3+Z8PK&X+(fP`*grL3322)V9+c(y*F2nkkw$np^6sn!B1d zT0yfylc2o_IHX5VBae13)Uy!k;n8l^=EMCZS|zkq18raJ8SQrPMsMvk?MO|QX1Hd* zW{@UVvrYY1ouO%~4OcG)jFeZa(#+C~)ZEj2R3~dt%}mV!jigpHkP- z+*dUQWR6~QN##?|R9C8}sVk)?dGWy0Y43+kfu*`KUC%bY=Ov@^yYWe(i!s2lof0=odv7 zr2?>V1;J#94$!y1fou1fKNpY$EWV+^M{cPiaY`Y+7D&CDph$v86)2(-vr2<@3L^fc6pRdH5)W}fLhxH{pgaC0?g_o z)dr!>!WnkbhkPlOm`)8)71w-uOj1Jv6Uv@~d4;J3iNvUjrUfKmIYq*XJN`AVyL4xsdY zsGDoXXyl5z3M!~e&{g1d`5jm@=men8)bgJ4AbB(SQrSaUFZE{iDg;HF{SV6pc&b^! zH-m2~t*R~RKkA#xNL6#iX2o#muNz@LQYvBdcAI@uD)ue>aVOBphb7fuL1hB zllFx6f1`eu=BuVc6{(JgvEN$zAIw?pkt|p*H0tr{1FBD|&B`L>V!$T01`KP8B1ho| z_O}8&+iI8{#%Nz?GvV2yAN2P>vf=W(A-_Y0Do-oT$VQ|9=FENS78*pEq-+fH!bok7 zwl=aI@v3I3b^yi{1&rs1;P8-c%2UeznoP}BtplkKbIny%xVnvEzhZ^FOuirdk)iad zf|b3L7lH#rItT3yvH?foTt$UqrM47i{t<{8#&F}{UBP5PT7V`vJQ&wZ*3?E1p)Fy} z`KIck&HyxYjPi_f4q&xvs*b331C|d7jsX_L%fZuSA7z`B!77bngkrkj9o_4J@yLGgfL>#01iyrFhzx+y;^0~Kk2iK`=j9#TVgMb<$6QPxy` zR*9-=tG1}FYDDcVm>a&zqZETfazi!;R|NM|TvY6U`!TA6s&;@)8y0duq*&fo@lqY5 z*{PKw-2vm5sbbXgR0XP|fQ~#H>;4MEYW)2bb4fbIg!Ve1q>6k9_6h5XO9 z*YY}wJ%Dd~1Y@NZV0+``r=e}v%X5^iRcinX_zIAOv~s1gUɍ)X7ft-z(-0Z_L+ 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 Vv0|mMjxCTc%YVx6WRILE{~xn&YM%fA diff --git a/examples/FtcRobotController/src/main/res/raw/silver.wav b/examples/FtcRobotController/src/main/res/raw/silver.wav deleted file mode 100644 index 25918a72ae51c412a9a6268279b449fd19639272..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 99068 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

- * Your ability to control hardware from this method will be limited. - */ - @Override - public void stop() { - - } -} diff --git a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptRampMotorSpeed.java b/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptRampMotorSpeed.java deleted file mode 100644 index 6e0be37..0000000 --- a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptRampMotorSpeed.java +++ /dev/null @@ -1,114 +0,0 @@ -/* Copyright (c) 2017 FIRST. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted (subject to the limitations in the disclaimer below) provided that - * the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list - * of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this - * list of conditions and the following disclaimer in the documentation and/or - * other materials provided with the distribution. - * - * Neither the name of FIRST nor the names of its contributors may be used to endorse or - * promote products derived from this software without specific prior written permission. - * - * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS - * LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.firstinspires.ftc.robotcontroller.external.samples; - -import com.qualcomm.robotcore.eventloop.opmode.Disabled; -import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode; -import com.qualcomm.robotcore.eventloop.opmode.TeleOp; -import com.qualcomm.robotcore.hardware.DcMotor; - -/* - * This OpMode ramps a single motor speed up and down repeatedly until Stop is pressed. - * The code is structured as a LinearOpMode - * - * This code assumes a DC motor configured with the name "left_drive" as is found on a Robot. - * - * INCREMENT sets how much to increase/decrease the power each cycle - * CYCLE_MS sets the update period. - * - * Use Android Studio to Copy this Class, and Paste it into your team's code folder with a new name. - * Remove or comment out the @Disabled line to add this OpMode to the Driver Station OpMode list - */ -@TeleOp(name = "Concept: Ramp Motor Speed", group = "Concept") -@Disabled -public class ConceptRampMotorSpeed extends LinearOpMode { - - static final double INCREMENT = 0.01; // amount to ramp motor each CYCLE_MS cycle - static final int CYCLE_MS = 50; // period of each cycle - static final double MAX_FWD = 1.0; // Maximum FWD power applied to motor - static final double MAX_REV = -1.0; // Maximum REV power applied to motor - - // Define class members - DcMotor motor; - double power = 0; - boolean rampUp = true; - - - @Override - public void runOpMode() { - - // Connect to motor (Assume standard left wheel) - // Change the text in quotes to match any motor name on your robot. - motor = hardwareMap.get(DcMotor.class, "left_drive"); - - // Wait for the start button - telemetry.addData(">", "Press Start to run Motors." ); - telemetry.update(); - waitForStart(); - - // Ramp motor speeds till stop pressed. - while(opModeIsActive()) { - - // Ramp the motors, according to the rampUp variable. - if (rampUp) { - // Keep stepping up until we hit the max value. - power += INCREMENT ; - if (power >= MAX_FWD ) { - power = MAX_FWD; - rampUp = !rampUp; // Switch ramp direction - } - } - else { - // Keep stepping down until we hit the min value. - power -= INCREMENT ; - if (power <= MAX_REV ) { - power = MAX_REV; - rampUp = !rampUp; // Switch ramp direction - } - } - - // Display the current value - telemetry.addData("Motor Power", "%5.2f", power); - telemetry.addData(">", "Press Stop to end test." ); - telemetry.update(); - - // Set the motor to the new power and pause; - motor.setPower(power); - sleep(CYCLE_MS); - idle(); - } - - // Turn off motor and signal done; - motor.setPower(0); - telemetry.addData(">", "Done"); - telemetry.update(); - - } -} diff --git a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptRevLED.java b/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptRevLED.java deleted file mode 100644 index 9c168d5..0000000 --- a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptRevLED.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - Copyright (c) 2024 Alan Smith - All rights reserved. - Redistribution and use in source and binary forms, with or without modification, - are permitted (subject to the limitations in the disclaimer below) provided that - the following conditions are met: - Redistributions of source code must retain the above copyright notice, this list - of conditions and the following disclaimer. - Redistributions in binary form must reproduce the above copyright notice, this - list of conditions and the following disclaimer in the documentation and/or - other materials provided with the distribution. - Neither the name of Alan Smith nor the names of its contributors may be used to - endorse or promote products derived from this software without specific prior - written permission. - NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS - LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESSFOR A PARTICULAR PURPOSE - ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR - TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ -package org.firstinspires.ftc.robotcontroller.external.samples; - -/* - * This OpMode illustrates how to use the REV Digital Indicator - * - * This is a simple way to add the REV Digital Indicator which has a red and green LED. - * (and as you may remember, red + green = yellow so when they are both on you can get yellow) - * - * Why? - * You can use this to show information to the driver. For example, green might be that you can - * pick up more game elements and red would be that you already have the possession limit. - * - * This OpMode assumes that the REV Digital Indicator is setup as 2 Digital Channels named - * front_led_green and front_led_red. (the green should be the lower of the 2 channels it is plugged - * into and the red should be the higher) - * - * Use Android Studio to Copy this Class, and Paste it into your team's code folder with a new name. - * Remove or comment out the @Disabled line to add this OpMode to the Driver Station OpMode list - * - * You can buy this product here: https://www.revrobotics.com/rev-31-2010/ - */ -import com.qualcomm.robotcore.eventloop.opmode.Disabled; -import com.qualcomm.robotcore.eventloop.opmode.OpMode; -import com.qualcomm.robotcore.eventloop.opmode.TeleOp; -import com.qualcomm.robotcore.hardware.LED; - -@TeleOp(name = "Concept: RevLED", group = "Concept") -@Disabled -public class ConceptRevLED extends OpMode { - LED frontLED_red; - LED frontLED_green; - - @Override - public void init() { - frontLED_green = hardwareMap.get(LED.class, "front_led_green"); - frontLED_red = hardwareMap.get(LED.class, "front_led_red"); - } - - @Override - public void loop() { - if (gamepad1.a) { - frontLED_red.on(); - } else { - frontLED_red.off(); - } - if (gamepad1.b) { - frontLED_green.on(); - } else { - frontLED_green.off(); - } - } -} diff --git a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptRevSPARKMini.java b/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptRevSPARKMini.java deleted file mode 100644 index 798d689..0000000 --- a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptRevSPARKMini.java +++ /dev/null @@ -1,111 +0,0 @@ -/* Copyright (c) 2018 FIRST. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted (subject to the limitations in the disclaimer below) provided that - * the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list - * of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this - * list of conditions and the following disclaimer in the documentation and/or - * other materials provided with the distribution. - * - * Neither the name of FIRST nor the names of its contributors may be used to endorse or - * promote products derived from this software without specific prior written permission. - * - * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS - * LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.firstinspires.ftc.robotcontroller.external.samples; - -import com.qualcomm.robotcore.eventloop.opmode.Disabled; -import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode; -import com.qualcomm.robotcore.eventloop.opmode.TeleOp; -import com.qualcomm.robotcore.hardware.DcMotorSimple; -import com.qualcomm.robotcore.util.ElapsedTime; -import com.qualcomm.robotcore.util.Range; - - -/* - * This OpMode demonstrates a POV Drive system, with commented-out code for a Tank Drive system, - * for a two wheeled robot using two REV SPARKminis. - * To use this example, connect two REV SPARKminis into servo ports on the Control Hub. On the - * robot configuration, use the drop down list under 'Servos' to select 'REV SPARKmini Controller' - * and name them 'left_drive' and 'right_drive'. - * - * Use Android Studio to Copy this Class, and Paste it into your team's code folder with a new name. - * Remove or comment out the @Disabled line to add this OpMode to the Driver Station OpMode list - */ - -@TeleOp(name="REV SPARKmini Simple Drive Example", group="Concept") -@Disabled -public class ConceptRevSPARKMini extends LinearOpMode { - - // Declare OpMode members. - private ElapsedTime runtime = new ElapsedTime(); - private DcMotorSimple leftDrive = null; - private DcMotorSimple rightDrive = null; - - @Override - public void runOpMode() { - telemetry.addData("Status", "Initialized"); - telemetry.update(); - - // Initialize the hardware variables. Note that the strings used here as parameters - // to 'get' must correspond to the names assigned during robot configuration. - leftDrive = hardwareMap.get(DcMotorSimple.class, "left_drive"); - rightDrive = hardwareMap.get(DcMotorSimple.class, "right_drive"); - - // Most robots need the motor on one side to be reversed to drive forward - // Reverse the motor that runs backward when connected directly to the battery - leftDrive.setDirection(DcMotorSimple.Direction.FORWARD); - rightDrive.setDirection(DcMotorSimple.Direction.REVERSE); - - // Wait for the game to start (driver presses START) - waitForStart(); - runtime.reset(); - - // run until the end of the match (driver presses STOP) - while (opModeIsActive()) { - - // Setup a variable for each drive wheel to save power level for telemetry - double leftPower; - double rightPower; - - // Choose to drive using either Tank Mode, or POV Mode - // Comment out the method that's not used. The default below is POV. - - // POV Mode uses left stick to go forward, and right stick to turn. - // - This uses basic math to combine motions and is easier to drive straight. - double drive = -gamepad1.left_stick_y; - double turn = gamepad1.right_stick_x; - leftPower = Range.clip(drive + turn, -1.0, 1.0) ; - rightPower = Range.clip(drive - turn, -1.0, 1.0) ; - - // Tank Mode uses one stick to control each wheel. - // - This requires no math, but it is hard to drive forward slowly and keep straight. - // leftPower = -gamepad1.left_stick_y ; - // rightPower = -gamepad1.right_stick_y ; - - // Send calculated power to wheels - leftDrive.setPower(leftPower); - rightDrive.setPower(rightPower); - - // Show the elapsed game time and wheel power. - telemetry.addData("Status", "Run Time: " + runtime.toString()); - telemetry.addData("Motors", "left (%.2f), right (%.2f)", leftPower, rightPower); - telemetry.update(); - } - } -} diff --git a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptScanServo.java b/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptScanServo.java deleted file mode 100644 index 2b8ad33..0000000 --- a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptScanServo.java +++ /dev/null @@ -1,115 +0,0 @@ -/* Copyright (c) 2017 FIRST. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted (subject to the limitations in the disclaimer below) provided that - * the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list - * of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this - * list of conditions and the following disclaimer in the documentation and/or - * other materials provided with the distribution. - * - * Neither the name of FIRST nor the names of its contributors may be used to endorse or - * promote products derived from this software without specific prior written permission. - * - * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS - * LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.firstinspires.ftc.robotcontroller.external.samples; - -import com.qualcomm.robotcore.eventloop.opmode.Disabled; -import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode; -import com.qualcomm.robotcore.eventloop.opmode.TeleOp; -import com.qualcomm.robotcore.hardware.Servo; - -/* - * This OpMode scans a single servo back and forward until Stop is pressed. - * The code is structured as a LinearOpMode - * INCREMENT sets how much to increase/decrease the servo position each cycle - * CYCLE_MS sets the update period. - * - * This code assumes a Servo configured with the name "left_hand" as is found on a Robot. - * - * NOTE: When any servo position is set, ALL attached servos are activated, so ensure that any other - * connected servos are able to move freely before running this test. - * - * Use Android Studio to Copy this Class, and Paste it into your team's code folder with a new name. - * Remove or comment out the @Disabled line to add this OpMode to the Driver Station OpMode list - */ -@TeleOp(name = "Concept: Scan Servo", group = "Concept") -@Disabled -public class ConceptScanServo extends LinearOpMode { - - static final double INCREMENT = 0.01; // amount to slew servo each CYCLE_MS cycle - static final int CYCLE_MS = 50; // period of each cycle - static final double MAX_POS = 1.0; // Maximum rotational position - static final double MIN_POS = 0.0; // Minimum rotational position - - // Define class members - Servo servo; - double position = (MAX_POS - MIN_POS) / 2; // Start at halfway position - boolean rampUp = true; - - - @Override - public void runOpMode() { - - // Connect to servo (Assume Robot Left Hand) - // Change the text in quotes to match any servo name on your robot. - servo = hardwareMap.get(Servo.class, "left_hand"); - - // Wait for the start button - telemetry.addData(">", "Press Start to scan Servo." ); - telemetry.update(); - waitForStart(); - - - // Scan servo till stop pressed. - while(opModeIsActive()){ - - // slew the servo, according to the rampUp (direction) variable. - if (rampUp) { - // Keep stepping up until we hit the max value. - position += INCREMENT ; - if (position >= MAX_POS ) { - position = MAX_POS; - rampUp = !rampUp; // Switch ramp direction - } - } - else { - // Keep stepping down until we hit the min value. - position -= INCREMENT ; - if (position <= MIN_POS ) { - position = MIN_POS; - rampUp = !rampUp; // Switch ramp direction - } - } - - // Display the current value - telemetry.addData("Servo Position", "%5.2f", position); - telemetry.addData(">", "Press Stop to end test." ); - telemetry.update(); - - // Set the servo to the new position and pause; - servo.setPosition(position); - sleep(CYCLE_MS); - idle(); - } - - // Signal done; - telemetry.addData(">", "Done"); - telemetry.update(); - } -} diff --git a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptSoundsASJava.java b/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptSoundsASJava.java deleted file mode 100644 index 1ceaa17..0000000 --- a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptSoundsASJava.java +++ /dev/null @@ -1,133 +0,0 @@ -/* Copyright (c) 2018 FIRST. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted (subject to the limitations in the disclaimer below) provided that - * the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list - * of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this - * list of conditions and the following disclaimer in the documentation and/or - * other materials provided with the distribution. - * - * Neither the name of FIRST nor the names of its contributors may be used to endorse or - * promote products derived from this software without specific prior written permission. - * - * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS - * LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.firstinspires.ftc.robotcontroller.external.samples; - -import com.qualcomm.ftccommon.SoundPlayer; -import com.qualcomm.robotcore.eventloop.opmode.Disabled; -import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode; -import com.qualcomm.robotcore.eventloop.opmode.TeleOp; - -/* - * This OpMode demonstrates how to play simple sounds on both the RC and DS phones. - * It illustrates how to build sounds into your application as a resource. - * This technique is best suited for use with Android Studio since it assumes you will be creating a new application - * - * If you are using OnBotJava, please see the ConceptSoundsOnBotJava sample - * - * Use Android Studio to Copy this Class, and Paste it into your team's code folder with a new name. - * Remove or comment out the @Disabled line to add this OpMode to the Driver Station OpMode list - * - * Operation: - * - * Gamepad X & B buttons are used to trigger sounds in this example, but any event can be used. - * Note: Time should be allowed for sounds to complete before playing other sounds. - * - * For sound files to be used as a compiled-in resource, they need to be located in a folder called "raw" under your "res" (resources) folder. - * You can create your own "raw" folder from scratch, or you can copy the one from the FtcRobotController module. - * - * Android Studio coders will ultimately need a folder in your path as follows: - * /TeamCode/src/main/res/raw - * - * Copy any .wav files you want to play into this folder. - * Make sure that your files ONLY use lower-case characters, and have no spaces or special characters other than underscore. - * - * The name you give your .wav files will become the resource ID for these sounds. - * eg: gold.wav becomes R.raw.gold - * - * If you wish to use the sounds provided for this sample, they are located in: - * /FtcRobotController/src/main/res/raw - * You can copy and paste the entire 'raw' folder using Android Studio. - * - */ - -@TeleOp(name="Concept: Sound Resources", group="Concept") -@Disabled -public class ConceptSoundsASJava extends LinearOpMode { - - // Declare OpMode members. - private boolean goldFound; // Sound file present flags - private boolean silverFound; - - private boolean isX = false; // Gamepad button state variables - private boolean isB = false; - - private boolean wasX = false; // Gamepad button history variables - private boolean WasB = false; - - @Override - public void runOpMode() { - - // Determine Resource IDs for sounds built into the RC application. - int silverSoundID = hardwareMap.appContext.getResources().getIdentifier("silver", "raw", hardwareMap.appContext.getPackageName()); - int goldSoundID = hardwareMap.appContext.getResources().getIdentifier("gold", "raw", hardwareMap.appContext.getPackageName()); - - // Determine if sound resources are found. - // Note: Preloading is NOT required, but it's a good way to verify all your sounds are available before you run. - if (goldSoundID != 0) - goldFound = SoundPlayer.getInstance().preload(hardwareMap.appContext, goldSoundID); - - if (silverSoundID != 0) - silverFound = SoundPlayer.getInstance().preload(hardwareMap.appContext, silverSoundID); - - // Display sound status - telemetry.addData("gold resource", goldFound ? "Found" : "NOT found\n Add gold.wav to /src/main/res/raw" ); - telemetry.addData("silver resource", silverFound ? "Found" : "Not found\n Add silver.wav to /src/main/res/raw" ); - - // Wait for the game to start (driver presses START) - telemetry.addData(">", "Press Start to continue"); - telemetry.update(); - waitForStart(); - - telemetry.addData(">", "Press X, B to play sounds."); - telemetry.update(); - - // run until the end of the match (driver presses STOP) - while (opModeIsActive()) { - - // say Silver each time gamepad X is pressed (This sound is a resource) - if (silverFound && (isX = gamepad1.x) && !wasX) { - SoundPlayer.getInstance().startPlaying(hardwareMap.appContext, silverSoundID); - telemetry.addData("Playing", "Resource Silver"); - telemetry.update(); - } - - // say Gold each time gamepad B is pressed (This sound is a resource) - if (goldFound && (isB = gamepad1.b) && !WasB) { - SoundPlayer.getInstance().startPlaying(hardwareMap.appContext, goldSoundID); - telemetry.addData("Playing", "Resource Gold"); - telemetry.update(); - } - - // Save last button states - wasX = isX; - WasB = isB; - } - } -} diff --git a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptSoundsOnBotJava.java b/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptSoundsOnBotJava.java deleted file mode 100644 index fbb7416..0000000 --- a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptSoundsOnBotJava.java +++ /dev/null @@ -1,120 +0,0 @@ -/* Copyright (c) 2018 FIRST. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted (subject to the limitations in the disclaimer below) provided that - * the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list - * of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this - * list of conditions and the following disclaimer in the documentation and/or - * other materials provided with the distribution. - * - * Neither the name of FIRST nor the names of its contributors may be used to endorse or - * promote products derived from this software without specific prior written permission. - * - * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS - * LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.firstinspires.ftc.robotcontroller.external.samples; - -import com.qualcomm.ftccommon.SoundPlayer; -import com.qualcomm.robotcore.eventloop.opmode.Disabled; -import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode; -import com.qualcomm.robotcore.eventloop.opmode.TeleOp; - -import java.io.File; - -/* - * This OpMode demonstrates how to play simple sounds on both the RC and DS phones. - * It illustrates how to play sound files that have been copied to the RC Phone - * This technique is best suited for use with OnBotJava since it does not require the app to be modified. - * - * Operation: - * - * Gamepad X & B buttons are used to trigger sounds in this example, but any event can be used. - * Note: Time should be allowed for sounds to complete before playing other sounds. - * - * To play a new sound, you will need to copy the .wav files to the phone, and then provide the full path to them as part of your OpMode. - * This is done in this sample for the two sound files. silver.wav and gold.wav - * - * You can put the files in a variety of soundPaths, but we recommend you put them in the /FIRST/blocks/sounds folder. - * Your OpModes will have guaranteed access to this folder, and you can transfer files into this folder using the BLOCKS web page. - * -- There is a link called "sounds" on the right hand side of the color bar on the BLOCKS page that can be used to send sound files to this folder by default. - * Or you can use Windows File Manager, or ADB to transfer the sound files - * - * To get full use of THIS sample, you will need to copy two sound file called silver.wav and gold.wav to /FIRST/blocks/sounds on the RC phone. - * They can be located here: - * https://github.com/ftctechnh/ftc_app/tree/master/FtcRobotController/src/main/res/raw/gold.wav - * https://github.com/ftctechnh/ftc_app/tree/master/FtcRobotController/src/main/res/raw/silver.wav - */ - -@TeleOp(name="Concept: Sound Files", group="Concept") -@Disabled -public class ConceptSoundsOnBotJava extends LinearOpMode { - - // Point to sound files on the phone's drive - private String soundPath = "/FIRST/blocks/sounds"; - private File goldFile = new File("/sdcard" + soundPath + "/gold.wav"); - private File silverFile = new File("/sdcard" + soundPath + "/silver.wav"); - - // Declare OpMode members. - private boolean isX = false; // Gamepad button state variables - private boolean isB = false; - - private boolean wasX = false; // Gamepad button history variables - private boolean WasB = false; - - @Override - public void runOpMode() { - - // Make sure that the sound files exist on the phone - boolean goldFound = goldFile.exists(); - boolean silverFound = silverFile.exists(); - - // Display sound status - telemetry.addData("gold sound", goldFound ? "Found" : "NOT Found \nCopy gold.wav to " + soundPath ); - telemetry.addData("silver sound", silverFound ? "Found" : "NOT Found \nCopy silver.wav to " + soundPath ); - - // Wait for the game to start (driver presses PLAY) - telemetry.addData(">", "Press Start to continue"); - telemetry.update(); - waitForStart(); - - telemetry.addData(">", "Press X or B to play sounds."); - telemetry.update(); - - // run until the end of the match (driver presses STOP) - while (opModeIsActive()) { - - // say Silver each time gamepad X is pressed (This sound is a resource) - if (silverFound && (isX = gamepad1.x) && !wasX) { - SoundPlayer.getInstance().startPlaying(hardwareMap.appContext, silverFile); - telemetry.addData("Playing", "Silver File"); - telemetry.update(); - } - - // say Gold each time gamepad B is pressed (This sound is a resource) - if (goldFound && (isB = gamepad1.b) && !WasB) { - SoundPlayer.getInstance().startPlaying(hardwareMap.appContext, goldFile); - telemetry.addData("Playing", "Gold File"); - telemetry.update(); - } - - // Save last button states - wasX = isX; - WasB = isB; - } - } -} diff --git a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptSoundsSKYSTONE.java b/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptSoundsSKYSTONE.java deleted file mode 100644 index 983e434..0000000 --- a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptSoundsSKYSTONE.java +++ /dev/null @@ -1,122 +0,0 @@ -/* Copyright (c) 2018 FIRST. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted (subject to the limitations in the disclaimer below) provided that - * the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list - * of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this - * list of conditions and the following disclaimer in the documentation and/or - * other materials provided with the distribution. - * - * Neither the name of FIRST nor the names of its contributors may be used to endorse or - * promote products derived from this software without specific prior written permission. - * - * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS - * LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.firstinspires.ftc.robotcontroller.external.samples; - -import android.content.Context; -import com.qualcomm.ftccommon.SoundPlayer; -import com.qualcomm.robotcore.eventloop.opmode.Disabled; -import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode; -import com.qualcomm.robotcore.eventloop.opmode.TeleOp; - -/* - * This OpMode demonstrates how to play one of the several SKYSTONE/Star Wars sounds loaded into the SDK. - * It does this by creating a simple "chooser" controlled by the gamepad Up Down buttons. - * This code also prevents sounds from stacking up by setting a "playing" flag, which is cleared when the sound finishes playing. - * - * Use Android Studio to Copy this Class, and Paste it into your team's code folder with a new name. - * Remove or comment out the @Disabled line to add this OpMode to the Driver Station OpMode list - * - * Operation: - * Use the DPAD to change the selected sound, and the Right Bumper to play it. - */ - -@TeleOp(name="SKYSTONE Sounds", group="Concept") -@Disabled -public class ConceptSoundsSKYSTONE extends LinearOpMode { - - // List of available sound resources - String sounds[] = {"ss_alarm", "ss_bb8_down", "ss_bb8_up", "ss_darth_vader", "ss_fly_by", - "ss_mf_fail", "ss_laser", "ss_laser_burst", "ss_light_saber", "ss_light_saber_long", "ss_light_saber_short", - "ss_light_speed", "ss_mine", "ss_power_up", "ss_r2d2_up", "ss_roger_roger", "ss_siren", "ss_wookie" }; - boolean soundPlaying = false; - - @Override - public void runOpMode() { - - // Variables for choosing from the available sounds - int soundIndex = 0; - int soundID = -1; - boolean was_dpad_up = false; - boolean was_dpad_down = false; - - Context myApp = hardwareMap.appContext; - - // create a sound parameter that holds the desired player parameters. - SoundPlayer.PlaySoundParams params = new SoundPlayer.PlaySoundParams(); - params.loopControl = 0; - params.waitForNonLoopingSoundsToFinish = true; - - // In this sample, we will skip waiting for the user to press play, and start displaying sound choices right away - while (!isStopRequested()) { - - // Look for DPAD presses to change the selection - if (gamepad1.dpad_down && !was_dpad_down) { - // Go to next sound (with list wrap) and display it - soundIndex = (soundIndex + 1) % sounds.length; - } - - if (gamepad1.dpad_up && !was_dpad_up) { - // Go to previous sound (with list wrap) and display it - soundIndex = (soundIndex + sounds.length - 1) % sounds.length; - } - - // Look for trigger to see if we should play sound - // Only start a new sound if we are currently not playing one. - if (gamepad1.right_bumper && !soundPlaying) { - - // Determine Resource IDs for the sounds you want to play, and make sure it's valid. - if ((soundID = myApp.getResources().getIdentifier(sounds[soundIndex], "raw", myApp.getPackageName())) != 0){ - - // Signal that the sound is now playing. - soundPlaying = true; - - // Start playing, and also Create a callback that will clear the playing flag when the sound is complete. - SoundPlayer.getInstance().startPlaying(myApp, soundID, params, null, - new Runnable() { - public void run() { - soundPlaying = false; - }} ); - } - } - - // Remember the last state of the dpad to detect changes. - was_dpad_up = gamepad1.dpad_up; - was_dpad_down = gamepad1.dpad_down; - - // Display the current sound choice, and the playing status. - telemetry.addData("", "Use DPAD up/down to choose sound."); - telemetry.addData("", "Press Right Bumper to play sound."); - telemetry.addData("", ""); - telemetry.addData("Sound >", sounds[soundIndex]); - telemetry.addData("Status >", soundPlaying ? "Playing" : "Stopped"); - telemetry.update(); - } - } -} diff --git a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptTelemetry.java b/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptTelemetry.java deleted file mode 100644 index f2c6097..0000000 --- a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptTelemetry.java +++ /dev/null @@ -1,177 +0,0 @@ -/* Copyright (c) 2017 FIRST. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted (subject to the limitations in the disclaimer below) provided that - * the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list - * of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this - * list of conditions and the following disclaimer in the documentation and/or - * other materials provided with the distribution. - * - * Neither the name of FIRST nor the names of its contributors may be used to endorse or - * promote products derived from this software without specific prior written permission. - * - * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS - * LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.firstinspires.ftc.robotcontroller.external.samples; - -import com.qualcomm.robotcore.eventloop.opmode.Disabled; -import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode; -import com.qualcomm.robotcore.eventloop.opmode.TeleOp; -import com.qualcomm.robotcore.hardware.VoltageSensor; -import com.qualcomm.robotcore.util.ElapsedTime; -import org.firstinspires.ftc.robotcore.external.Func; -import org.firstinspires.ftc.robotcore.external.Telemetry; - -/* - * This OpMode illustrates various ways in which telemetry can be - * transmitted from the robot controller to the driver station. The sample illustrates - * numeric and text data, formatted output, and optimized evaluation of expensive-to-acquire - * information. The telemetry log is illustrated by scrolling a poem - * to the driver station. - * - * Also see the Telemetry javadocs. - */ -@TeleOp(name = "Concept: Telemetry", group = "Concept") -@Disabled -public class ConceptTelemetry extends LinearOpMode { - /** Keeps track of the line of the poem which is to be emitted next */ - int poemLine = 0; - - /** Keeps track of how long it's been since we last emitted a line of poetry */ - ElapsedTime poemElapsed = new ElapsedTime(); - - static final String[] poem = new String[] { - - "Mary had a little lamb,", - "His fleece was white as snow,", - "And everywhere that Mary went,", - "The lamb was sure to go.", - "", - "He followed her to school one day,", - "Which was against the rule,", - "It made the children laugh and play", - "To see a lamb at school.", - "", - "And so the teacher turned it out,", - "But still it lingered near,", - "And waited patiently about,", - "Till Mary did appear.", - "", - "\"Why does the lamb love Mary so?\"", - "The eager children cry.", - "\"Why, Mary loves the lamb, you know,\"", - "The teacher did reply.", - "", - "" - }; - - @Override public void runOpMode() { - - /* we keep track of how long it's been since the OpMode was started, just - * to have some interesting data to show */ - ElapsedTime opmodeRunTime = new ElapsedTime(); - - // We show the log in oldest-to-newest order, as that's better for poetry - telemetry.log().setDisplayOrder(Telemetry.Log.DisplayOrder.OLDEST_FIRST); - // We can control the number of lines shown in the log - telemetry.log().setCapacity(6); - // The interval between lines of poetry, in seconds - double sPoemInterval = 0.6; - - /* - * Wait until we've been given the ok to go. For something to do, we emit the - * elapsed time as we sit here and wait. If we didn't want to do anything while - * we waited, we would just call waitForStart(). - */ - while (!isStarted()) { - telemetry.addData("time", "%.1f seconds", opmodeRunTime.seconds()); - telemetry.update(); - idle(); - } - - // Ok, we've been given the ok to go - - /* - * As an illustration, the first line on our telemetry display will display the battery voltage. - * The idea here is that it's expensive to compute the voltage (at least for purposes of illustration) - * so you don't want to do it unless the data is _actually_ going to make it to the - * driver station (recall that telemetry transmission is throttled to reduce bandwidth use. - * Note that getBatteryVoltage() below returns 'Infinity' if there's no voltage sensor attached. - * - * @see Telemetry#getMsTransmissionInterval() - */ - telemetry.addData("voltage", "%.1f volts", new Func() { - @Override public Double value() { - return getBatteryVoltage(); - } - }); - - // Reset to keep some timing stats for the post-'start' part of the OpMode - opmodeRunTime.reset(); - int loopCount = 1; - - // Go go gadget robot! - while (opModeIsActive()) { - - // Emit poetry if it's been a while - if (poemElapsed.seconds() > sPoemInterval) { - emitPoemLine(); - } - - // As an illustration, show some loop timing information - telemetry.addData("loop count", loopCount); - telemetry.addData("ms/loop", "%.3f ms", opmodeRunTime.milliseconds() / loopCount); - - // Show joystick information as some other illustrative data - telemetry.addLine("left joystick | ") - .addData("x", gamepad1.left_stick_x) - .addData("y", gamepad1.left_stick_y); - telemetry.addLine("right joystick | ") - .addData("x", gamepad1.right_stick_x) - .addData("y", gamepad1.right_stick_y); - - /* - * Transmit the telemetry to the driver station, subject to throttling. - * See the documentation for Telemetry.getMsTransmissionInterval() for more information. - */ - telemetry.update(); - - // Update loop info - loopCount++; - } - } - - // emits a line of poetry to the telemetry log - void emitPoemLine() { - telemetry.log().add(poem[poemLine]); - poemLine = (poemLine+1) % poem.length; - poemElapsed.reset(); - } - - // Computes the current battery voltage - double getBatteryVoltage() { - double result = Double.POSITIVE_INFINITY; - for (VoltageSensor sensor : hardwareMap.voltageSensor) { - double voltage = sensor.getVoltage(); - if (voltage > 0) { - result = Math.min(result, voltage); - } - } - return result; - } -} diff --git a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptVisionColorLocator.java b/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptVisionColorLocator.java deleted file mode 100644 index 987694d..0000000 --- a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptVisionColorLocator.java +++ /dev/null @@ -1,191 +0,0 @@ -/* - * Copyright (c) 2024 Phil Malone - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.firstinspires.ftc.robotcontroller.external.samples; - -import android.util.Size; - -import com.qualcomm.robotcore.eventloop.opmode.Disabled; -import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode; -import com.qualcomm.robotcore.eventloop.opmode.TeleOp; -import com.qualcomm.robotcore.util.SortOrder; - -import org.firstinspires.ftc.robotcore.external.Telemetry; -import org.firstinspires.ftc.robotcore.external.hardware.camera.WebcamName; -import org.firstinspires.ftc.vision.VisionPortal; -import org.firstinspires.ftc.vision.opencv.ColorBlobLocatorProcessor; -import org.firstinspires.ftc.vision.opencv.ColorRange; -import org.firstinspires.ftc.vision.opencv.ImageRegion; -import org.opencv.core.RotatedRect; - -import java.util.List; - -/* - * This OpMode illustrates how to use a video source (camera) to locate specifically colored regions - * - * Unlike a "color sensor" which determines the color of an object in the field of view, this "color locator" - * will search the Region Of Interest (ROI) in a camera image, and find any "blobs" of color that match the requested color range. - * These blobs can be further filtered and sorted to find the one most likely to be the item the user is looking for. - * - * To perform this function, a VisionPortal runs a ColorBlobLocatorProcessor process. - * The ColorBlobLocatorProcessor process is created first, and then the VisionPortal is built to use this process. - * The ColorBlobLocatorProcessor analyses the ROI and locates pixels that match the ColorRange to form a "mask". - * The matching pixels are then collected into contiguous "blobs" of pixels. The outer boundaries of these blobs are called its "contour". - * For each blob, the process then creates the smallest possible rectangle "boxFit" that will fully encase the contour. - * The user can then call getBlobs() to retrieve the list of Blobs, where each Blob contains the contour and the boxFit data. - * Note: The default sort order for Blobs is ContourArea, in descending order, so the biggest contours are listed first. - * - * To aid the user, a colored boxFit rectangle is drawn on the camera preview to show the location of each Blob - * The original Blob contour can also be added to the preview. This is helpful when configuring the ColorBlobLocatorProcessor parameters. - * - * Use Android Studio to Copy this Class, and Paste it into your team's code folder with a new name. - * Remove or comment out the @Disabled line to add this OpMode to the Driver Station OpMode list - */ - -@Disabled -@TeleOp(name = "Concept: Vision Color-Locator", group = "Concept") -public class ConceptVisionColorLocator extends LinearOpMode -{ - @Override - public void runOpMode() - { - /* Build a "Color Locator" vision processor based on the ColorBlobLocatorProcessor class. - * - Specify the color range you are looking for. You can use a predefined color, or create you own color range - * .setTargetColorRange(ColorRange.BLUE) // use a predefined color match - * Available predefined colors are: RED, BLUE YELLOW GREEN - * .setTargetColorRange(new ColorRange(ColorSpace.YCrCb, // or define your own color match - * new Scalar( 32, 176, 0), - * new Scalar(255, 255, 132))) - * - * - Focus the color locator by defining a RegionOfInterest (ROI) which you want to search. - * This can be the entire frame, or a sub-region defined using: - * 1) standard image coordinates or 2) a normalized +/- 1.0 coordinate system. - * Use one form of the ImageRegion class to define the ROI. - * ImageRegion.entireFrame() - * ImageRegion.asImageCoordinates(50, 50, 150, 150) 100x100 pixel square near the upper left corner - * ImageRegion.asUnityCenterCoordinates(-0.5, 0.5, 0.5, -0.5) 50% width/height square centered on screen - * - * - Define which contours are included. - * You can get ALL the contours, or you can skip any contours that are completely inside another contour. - * .setContourMode(ColorBlobLocatorProcessor.ContourMode.ALL_FLATTENED_HIERARCHY) // return all contours - * .setContourMode(ColorBlobLocatorProcessor.ContourMode.EXTERNAL_ONLY) // exclude contours inside other contours - * note: EXTERNAL_ONLY helps to avoid bright reflection spots from breaking up areas of solid color. - * - * - turn the display of contours ON or OFF. Turning this on helps debugging but takes up valuable CPU time. - * .setDrawContours(true) - * - * - include any pre-processing of the image or mask before looking for Blobs. - * There are some extra processing you can include to improve the formation of blobs. Using these features requires - * an understanding of how they may effect the final blobs. The "pixels" argument sets the NxN kernel size. - * .setBlurSize(int pixels) Blurring an image helps to provide a smooth color transition between objects, and smoother contours. - * The higher the number of pixels, the more blurred the image becomes. - * Note: Even "pixels" values will be incremented to satisfy the "odd number" requirement. - * Blurring too much may hide smaller features. A "pixels" size of 5 is good for a 320x240 image. - * .setErodeSize(int pixels) Erosion removes floating pixels and thin lines so that only substantive objects remain. - * Erosion can grow holes inside regions, and also shrink objects. - * "pixels" in the range of 2-4 are suitable for low res images. - * .setDilateSize(int pixels) Dilation makes objects more visible by filling in small holes, making lines appear thicker, - * and making filled shapes appear larger. Dilation is useful for joining broken parts of an - * object, such as when removing noise from an image. - * "pixels" in the range of 2-4 are suitable for low res images. - */ - ColorBlobLocatorProcessor colorLocator = new ColorBlobLocatorProcessor.Builder() - .setTargetColorRange(ColorRange.BLUE) // use a predefined color match - .setContourMode(ColorBlobLocatorProcessor.ContourMode.EXTERNAL_ONLY) // exclude blobs inside blobs - .setRoi(ImageRegion.asUnityCenterCoordinates(-0.5, 0.5, 0.5, -0.5)) // search central 1/4 of camera view - .setDrawContours(true) // Show contours on the Stream Preview - .setBlurSize(5) // Smooth the transitions between different colors in image - .build(); - - /* - * Build a vision portal to run the Color Locator process. - * - * - Add the colorLocator process created above. - * - Set the desired video resolution. - * Since a high resolution will not improve this process, choose a lower resolution that is - * supported by your camera. This will improve overall performance and reduce latency. - * - Choose your video source. This may be - * .setCamera(hardwareMap.get(WebcamName.class, "Webcam 1")) ..... for a webcam - * or - * .setCamera(BuiltinCameraDirection.BACK) ... for a Phone Camera - */ - VisionPortal portal = new VisionPortal.Builder() - .addProcessor(colorLocator) - .setCameraResolution(new Size(320, 240)) - .setCamera(hardwareMap.get(WebcamName.class, "Webcam 1")) - .build(); - - telemetry.setMsTransmissionInterval(50); // Speed up telemetry updates, Just use for debugging. - telemetry.setDisplayFormat(Telemetry.DisplayFormat.MONOSPACE); - - // WARNING: To be able to view the stream preview on the Driver Station, this code runs in INIT mode. - while (opModeIsActive() || opModeInInit()) - { - telemetry.addData("preview on/off", "... Camera Stream\n"); - - // Read the current list - List blobs = colorLocator.getBlobs(); - - /* - * The list of Blobs can be filtered to remove unwanted Blobs. - * Note: All contours will be still displayed on the Stream Preview, but only those that satisfy the filter - * conditions will remain in the current list of "blobs". Multiple filters may be used. - * - * Use any of the following filters. - * - * ColorBlobLocatorProcessor.Util.filterByArea(minArea, maxArea, blobs); - * A Blob's area is the number of pixels contained within the Contour. Filter out any that are too big or small. - * Start with a large range and then refine the range based on the likely size of the desired object in the viewfinder. - * - * ColorBlobLocatorProcessor.Util.filterByDensity(minDensity, maxDensity, blobs); - * A blob's density is an indication of how "full" the contour is. - * If you put a rubber band around the contour you would get the "Convex Hull" of the contour. - * The density is the ratio of Contour-area to Convex Hull-area. - * - * ColorBlobLocatorProcessor.Util.filterByAspectRatio(minAspect, maxAspect, blobs); - * A blob's Aspect ratio is the ratio of boxFit long side to short side. - * A perfect Square has an aspect ratio of 1. All others are > 1 - */ - ColorBlobLocatorProcessor.Util.filterByArea(50, 20000, blobs); // filter out very small blobs. - - /* - * The list of Blobs can be sorted using the same Blob attributes as listed above. - * No more than one sort call should be made. Sorting can use ascending or descending order. - * ColorBlobLocatorProcessor.Util.sortByArea(SortOrder.DESCENDING, blobs); // Default - * ColorBlobLocatorProcessor.Util.sortByDensity(SortOrder.DESCENDING, blobs); - * ColorBlobLocatorProcessor.Util.sortByAspectRatio(SortOrder.DESCENDING, blobs); - */ - - telemetry.addLine(" Area Density Aspect Center"); - - // Display the size (area) and center location for each Blob. - for(ColorBlobLocatorProcessor.Blob b : blobs) - { - RotatedRect boxFit = b.getBoxFit(); - telemetry.addLine(String.format("%5d %4.2f %5.2f (%3d,%3d)", - b.getContourArea(), b.getDensity(), b.getAspectRatio(), (int) boxFit.center.x, (int) boxFit.center.y)); - } - - telemetry.update(); - sleep(50); - } - } -} diff --git a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptVisionColorSensor.java b/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptVisionColorSensor.java deleted file mode 100644 index 6be2bc4..0000000 --- a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptVisionColorSensor.java +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Copyright (c) 2024 Phil Malone - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.firstinspires.ftc.robotcontroller.external.samples; - -import android.graphics.Color; -import android.util.Size; - -import com.qualcomm.robotcore.eventloop.opmode.Disabled; -import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode; -import com.qualcomm.robotcore.eventloop.opmode.TeleOp; - -import org.firstinspires.ftc.robotcore.external.hardware.camera.WebcamName; -import org.firstinspires.ftc.vision.VisionPortal; -import org.firstinspires.ftc.vision.opencv.ImageRegion; -import org.firstinspires.ftc.vision.opencv.PredominantColorProcessor; - -/* - * This OpMode illustrates how to use a video source (camera) as a color sensor - * - * A "color sensor" will typically determine the color of the object that it is pointed at. - * - * This sample performs the same function, except it uses a video camera to inspect an object or scene. - * The user may choose to inspect all, or just a Region of Interest (ROI), of the active camera view. - * The user must also provide a list of "acceptable colors" (Swatches) from which the closest matching color will be selected. - * - * To perform this function, a VisionPortal runs a PredominantColorProcessor process. - * The PredominantColorProcessor process is created first, and then the VisionPortal is built to use this process. - * The PredominantColorProcessor analyses the ROI and splits the colored pixels into several color-clusters. - * The largest of these clusters is then considered to be the "Predominant Color" - * The process then matches the Predominant Color with the closest Swatch and returns that match. - * - * To aid the user, a colored rectangle is drawn on the camera preview to show the RegionOfInterest, - * The Predominant Color is used to paint the rectangle border, so the user can verify that the color is reasonable. - * - * Use Android Studio to Copy this Class, and Paste it into your team's code folder with a new name. - * Remove or comment out the @Disabled line to add this OpMode to the Driver Station OpMode list - */ - -@Disabled -@TeleOp(name = "Concept: Vision Color-Sensor", group = "Concept") -public class ConceptVisionColorSensor extends LinearOpMode -{ - @Override - public void runOpMode() - { - /* Build a "Color Sensor" vision processor based on the PredominantColorProcessor class. - * - * - Focus the color sensor by defining a RegionOfInterest (ROI) which you want to inspect. - * This can be the entire frame, or a sub-region defined using: - * 1) standard image coordinates or 2) a normalized +/- 1.0 coordinate system. - * Use one form of the ImageRegion class to define the ROI. - * ImageRegion.entireFrame() - * ImageRegion.asImageCoordinates(50, 50, 150, 150) 100x100 pixel square near the upper left corner - * ImageRegion.asUnityCenterCoordinates(-0.1, 0.1, 0.1, -0.1) 10% width/height square centered on screen - * - * - Set the list of "acceptable" color swatches (matches). - * Only colors that you assign here will be returned. - * If you know the sensor will be pointing to one of a few specific colors, enter them here. - * Or, if the sensor may be pointed randomly, provide some additional colors that may match the surrounding. - * Possible choices are: - * RED, ORANGE, YELLOW, GREEN, CYAN, BLUE, PURPLE, MAGENTA, BLACK, WHITE; - * - * Note that in the example shown below, only some of the available colors are included. - * This will force any other colored region into one of these colors. - * eg: Green may be reported as YELLOW, as this may be the "closest" match. - */ - PredominantColorProcessor colorSensor = new PredominantColorProcessor.Builder() - .setRoi(ImageRegion.asUnityCenterCoordinates(-0.1, 0.1, 0.1, -0.1)) - .setSwatches( - PredominantColorProcessor.Swatch.RED, - PredominantColorProcessor.Swatch.BLUE, - PredominantColorProcessor.Swatch.YELLOW, - PredominantColorProcessor.Swatch.BLACK, - PredominantColorProcessor.Swatch.WHITE) - .build(); - - /* - * Build a vision portal to run the Color Sensor process. - * - * - Add the colorSensor process created above. - * - Set the desired video resolution. - * Since a high resolution will not improve this process, choose a lower resolution that is - * supported by your camera. This will improve overall performance and reduce latency. - * - Choose your video source. This may be - * .setCamera(hardwareMap.get(WebcamName.class, "Webcam 1")) ..... for a webcam - * or - * .setCamera(BuiltinCameraDirection.BACK) ... for a Phone Camera - */ - VisionPortal portal = new VisionPortal.Builder() - .addProcessor(colorSensor) - .setCameraResolution(new Size(320, 240)) - .setCamera(hardwareMap.get(WebcamName.class, "Webcam 1")) - .build(); - - telemetry.setMsTransmissionInterval(50); // Speed up telemetry updates, Just use for debugging. - - // WARNING: To be able to view the stream preview on the Driver Station, this code runs in INIT mode. - while (opModeIsActive() || opModeInInit()) - { - telemetry.addData("DS preview on/off", "3 dots, Camera Stream\n"); - - // Request the most recent color analysis. - // This will return the closest matching colorSwatch and the predominant RGB color. - // Note: to take actions based on the detected color, simply use the colorSwatch in a comparison or switch. - // eg: - // if (result.closestSwatch == PredominantColorProcessor.Swatch.RED) {... some code ...} - PredominantColorProcessor.Result result = colorSensor.getAnalysis(); - - // Display the Color Sensor result. - telemetry.addData("Best Match:", result.closestSwatch); - telemetry.addLine(String.format("R %3d, G %3d, B %3d", Color.red(result.rgb), Color.green(result.rgb), Color.blue(result.rgb))); - telemetry.update(); - - sleep(20); - } - } -} diff --git a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/RobotAutoDriveByEncoder_Linear.java b/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/RobotAutoDriveByEncoder_Linear.java deleted file mode 100644 index 63293d0..0000000 --- a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/RobotAutoDriveByEncoder_Linear.java +++ /dev/null @@ -1,187 +0,0 @@ -/* Copyright (c) 2017 FIRST. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted (subject to the limitations in the disclaimer below) provided that - * the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list - * of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this - * list of conditions and the following disclaimer in the documentation and/or - * other materials provided with the distribution. - * - * Neither the name of FIRST nor the names of its contributors may be used to endorse or - * promote products derived from this software without specific prior written permission. - * - * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS - * LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.firstinspires.ftc.robotcontroller.external.samples; - -import com.qualcomm.robotcore.eventloop.opmode.Autonomous; -import com.qualcomm.robotcore.eventloop.opmode.Disabled; -import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode; -import com.qualcomm.robotcore.hardware.DcMotor; -import com.qualcomm.robotcore.util.ElapsedTime; - -/* - * This OpMode illustrates the concept of driving a path based on encoder counts. - * The code is structured as a LinearOpMode - * - * The code REQUIRES that you DO have encoders on the wheels, - * otherwise you would use: RobotAutoDriveByTime; - * - * This code ALSO requires that the drive Motors have been configured such that a positive - * power command moves them forward, and causes the encoders to count UP. - * - * The desired path in this example is: - * - Drive forward for 48 inches - * - Spin right for 12 Inches - * - Drive Backward for 24 inches - * - Stop and close the claw. - * - * The code is written using a method called: encoderDrive(speed, leftInches, rightInches, timeoutS) - * that performs the actual movement. - * This method assumes that each movement is relative to the last stopping place. - * There are other ways to perform encoder based moves, but this method is probably the simplest. - * This code uses the RUN_TO_POSITION mode to enable the Motor controllers to generate the run profile - * - * Use Android Studio to Copy this Class, and Paste it into your team's code folder with a new name. - * Remove or comment out the @Disabled line to add this OpMode to the Driver Station OpMode list - */ - -@Autonomous(name="Robot: Auto Drive By Encoder", group="Robot") -@Disabled -public class RobotAutoDriveByEncoder_Linear extends LinearOpMode { - - /* Declare OpMode members. */ - private DcMotor leftDrive = null; - private DcMotor rightDrive = null; - - private ElapsedTime runtime = new ElapsedTime(); - - // Calculate the COUNTS_PER_INCH for your specific drive train. - // Go to your motor vendor website to determine your motor's COUNTS_PER_MOTOR_REV - // For external drive gearing, set DRIVE_GEAR_REDUCTION as needed. - // For example, use a value of 2.0 for a 12-tooth spur gear driving a 24-tooth spur gear. - // This is gearing DOWN for less speed and more torque. - // For gearing UP, use a gear ratio less than 1.0. Note this will affect the direction of wheel rotation. - static final double COUNTS_PER_MOTOR_REV = 1440 ; // eg: TETRIX Motor Encoder - static final double DRIVE_GEAR_REDUCTION = 1.0 ; // No External Gearing. - static final double WHEEL_DIAMETER_INCHES = 4.0 ; // For figuring circumference - static final double COUNTS_PER_INCH = (COUNTS_PER_MOTOR_REV * DRIVE_GEAR_REDUCTION) / - (WHEEL_DIAMETER_INCHES * 3.1415); - static final double DRIVE_SPEED = 0.6; - static final double TURN_SPEED = 0.5; - - @Override - public void runOpMode() { - - // Initialize the drive system variables. - leftDrive = hardwareMap.get(DcMotor.class, "left_drive"); - rightDrive = hardwareMap.get(DcMotor.class, "right_drive"); - - // To drive forward, most robots need the motor on one side to be reversed, because the axles point in opposite directions. - // When run, this OpMode should start both motors driving forward. So adjust these two lines based on your first test drive. - // Note: The settings here assume direct drive on left and right wheels. Gear Reduction or 90 Deg drives may require direction flips - leftDrive.setDirection(DcMotor.Direction.REVERSE); - rightDrive.setDirection(DcMotor.Direction.FORWARD); - - leftDrive.setMode(DcMotor.RunMode.STOP_AND_RESET_ENCODER); - rightDrive.setMode(DcMotor.RunMode.STOP_AND_RESET_ENCODER); - - leftDrive.setMode(DcMotor.RunMode.RUN_USING_ENCODER); - rightDrive.setMode(DcMotor.RunMode.RUN_USING_ENCODER); - - // Send telemetry message to indicate successful Encoder reset - telemetry.addData("Starting at", "%7d :%7d", - leftDrive.getCurrentPosition(), - rightDrive.getCurrentPosition()); - telemetry.update(); - - // Wait for the game to start (driver presses START) - waitForStart(); - - // Step through each leg of the path, - // Note: Reverse movement is obtained by setting a negative distance (not speed) - encoderDrive(DRIVE_SPEED, 48, 48, 5.0); // S1: Forward 47 Inches with 5 Sec timeout - encoderDrive(TURN_SPEED, 12, -12, 4.0); // S2: Turn Right 12 Inches with 4 Sec timeout - encoderDrive(DRIVE_SPEED, -24, -24, 4.0); // S3: Reverse 24 Inches with 4 Sec timeout - - telemetry.addData("Path", "Complete"); - telemetry.update(); - sleep(1000); // pause to display final telemetry message. - } - - /* - * Method to perform a relative move, based on encoder counts. - * Encoders are not reset as the move is based on the current position. - * Move will stop if any of three conditions occur: - * 1) Move gets to the desired position - * 2) Move runs out of time - * 3) Driver stops the OpMode running. - */ - public void encoderDrive(double speed, - double leftInches, double rightInches, - double timeoutS) { - int newLeftTarget; - int newRightTarget; - - // Ensure that the OpMode is still active - if (opModeIsActive()) { - - // Determine new target position, and pass to motor controller - newLeftTarget = leftDrive.getCurrentPosition() + (int)(leftInches * COUNTS_PER_INCH); - newRightTarget = rightDrive.getCurrentPosition() + (int)(rightInches * COUNTS_PER_INCH); - leftDrive.setTargetPosition(newLeftTarget); - rightDrive.setTargetPosition(newRightTarget); - - // Turn On RUN_TO_POSITION - leftDrive.setMode(DcMotor.RunMode.RUN_TO_POSITION); - rightDrive.setMode(DcMotor.RunMode.RUN_TO_POSITION); - - // reset the timeout time and start motion. - runtime.reset(); - leftDrive.setPower(Math.abs(speed)); - rightDrive.setPower(Math.abs(speed)); - - // keep looping while we are still active, and there is time left, and both motors are running. - // Note: We use (isBusy() && isBusy()) in the loop test, which means that when EITHER motor hits - // its target position, the motion will stop. This is "safer" in the event that the robot will - // always end the motion as soon as possible. - // However, if you require that BOTH motors have finished their moves before the robot continues - // onto the next step, use (isBusy() || isBusy()) in the loop test. - while (opModeIsActive() && - (runtime.seconds() < timeoutS) && - (leftDrive.isBusy() && rightDrive.isBusy())) { - - // Display it for the driver. - telemetry.addData("Running to", " %7d :%7d", newLeftTarget, newRightTarget); - telemetry.addData("Currently at", " at %7d :%7d", - leftDrive.getCurrentPosition(), rightDrive.getCurrentPosition()); - telemetry.update(); - } - - // Stop all motion; - leftDrive.setPower(0); - rightDrive.setPower(0); - - // Turn off RUN_TO_POSITION - leftDrive.setMode(DcMotor.RunMode.RUN_USING_ENCODER); - rightDrive.setMode(DcMotor.RunMode.RUN_USING_ENCODER); - - sleep(250); // optional pause after each move. - } - } -} diff --git a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/RobotAutoDriveByGyro_Linear.java b/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/RobotAutoDriveByGyro_Linear.java deleted file mode 100644 index ab70934..0000000 --- a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/RobotAutoDriveByGyro_Linear.java +++ /dev/null @@ -1,429 +0,0 @@ -/* Copyright (c) 2022 FIRST. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted (subject to the limitations in the disclaimer below) provided that - * the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list - * of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this - * list of conditions and the following disclaimer in the documentation and/or - * other materials provided with the distribution. - * - * Neither the name of FIRST nor the names of its contributors may be used to endorse or - * promote products derived from this software without specific prior written permission. - * - * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS - * LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.firstinspires.ftc.robotcontroller.external.samples; - -import com.qualcomm.hardware.rev.RevHubOrientationOnRobot; -import com.qualcomm.robotcore.eventloop.opmode.Autonomous; -import com.qualcomm.robotcore.eventloop.opmode.Disabled; -import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode; -import com.qualcomm.robotcore.hardware.DcMotor; -import com.qualcomm.robotcore.hardware.IMU; -import com.qualcomm.robotcore.util.ElapsedTime; -import com.qualcomm.robotcore.util.Range; -import org.firstinspires.ftc.robotcore.external.navigation.AngleUnit; -import org.firstinspires.ftc.robotcore.external.navigation.YawPitchRollAngles; - -/* - * This OpMode illustrates the concept of driving an autonomous path based on Gyro (IMU) heading and encoder counts. - * The code is structured as a LinearOpMode - * - * The path to be followed by the robot is built from a series of drive, turn or pause steps. - * Each step on the path is defined by a single function call, and these can be strung together in any order. - * - * The code REQUIRES that you have encoders on the drive motors, otherwise you should use: RobotAutoDriveByTime; - * - * This code uses the Universal IMU interface so it will work with either the BNO055, or BHI260 IMU. - * To run as written, the Control/Expansion hub should be mounted horizontally on a flat part of the robot chassis. - * The REV Logo should be facing UP, and the USB port should be facing forward. - * If this is not the configuration of your REV Control Hub, then the code should be modified to reflect the correct orientation. - * - * This sample requires that the drive Motors have been configured with names : left_drive and right_drive. - * It also requires that a positive power command moves both motors forward, and causes the encoders to count UP. - * So please verify that both of your motors move the robot forward on the first move. If not, make the required correction. - * See the beginning of runOpMode() to set the FORWARD/REVERSE option for each motor. - * - * This code uses RUN_TO_POSITION mode for driving straight, and RUN_USING_ENCODER mode for turning and holding. - * Note: This code implements the requirement of calling setTargetPosition() at least once before switching to RUN_TO_POSITION mode. - * - * Notes: - * - * All angles are referenced to the coordinate-frame that is set whenever resetHeading() is called. - * In this sample, the heading is reset when the Start button is touched on the Driver Station. - * Note: It would be possible to reset the heading after each move, but this would accumulate steering errors. - * - * The angle of movement/rotation is assumed to be a standardized rotation around the robot Z axis, - * which means that a Positive rotation is Counter Clockwise, looking down on the field. - * This is consistent with the FTC field coordinate conventions set out in the document: - * https://ftc-docs.firstinspires.org/field-coordinate-system - * - * Control Approach. - * - * To reach, or maintain a required heading, this code implements a basic Proportional Controller where: - * - * Steering power = Heading Error * Proportional Gain. - * - * "Heading Error" is calculated by taking the difference between the desired heading and the actual heading, - * and then "normalizing" it by converting it to a value in the +/- 180 degree range. - * - * "Proportional Gain" is a constant that YOU choose to set the "strength" of the steering response. - * - * Use Android Studio to Copy this Class, and Paste it into your "TeamCode" folder with a new name. - * Remove or comment out the @Disabled line to add this OpMode to the Driver Station OpMode list - */ - -@Autonomous(name="Robot: Auto Drive By Gyro", group="Robot") -@Disabled -public class RobotAutoDriveByGyro_Linear extends LinearOpMode { - - /* Declare OpMode members. */ - private DcMotor leftDrive = null; - private DcMotor rightDrive = null; - private IMU imu = null; // Control/Expansion Hub IMU - - private double headingError = 0; - - // These variable are declared here (as class members) so they can be updated in various methods, - // but still be displayed by sendTelemetry() - private double targetHeading = 0; - private double driveSpeed = 0; - private double turnSpeed = 0; - private double leftSpeed = 0; - private double rightSpeed = 0; - private int leftTarget = 0; - private int rightTarget = 0; - - // Calculate the COUNTS_PER_INCH for your specific drive train. - // Go to your motor vendor website to determine your motor's COUNTS_PER_MOTOR_REV - // For external drive gearing, set DRIVE_GEAR_REDUCTION as needed. - // For example, use a value of 2.0 for a 12-tooth spur gear driving a 24-tooth spur gear. - // This is gearing DOWN for less speed and more torque. - // For gearing UP, use a gear ratio less than 1.0. Note this will affect the direction of wheel rotation. - static final double COUNTS_PER_MOTOR_REV = 537.7 ; // eg: GoBILDA 312 RPM Yellow Jacket - static final double DRIVE_GEAR_REDUCTION = 1.0 ; // No External Gearing. - static final double WHEEL_DIAMETER_INCHES = 4.0 ; // For figuring circumference - static final double COUNTS_PER_INCH = (COUNTS_PER_MOTOR_REV * DRIVE_GEAR_REDUCTION) / - (WHEEL_DIAMETER_INCHES * 3.1415); - - // These constants define the desired driving/control characteristics - // They can/should be tweaked to suit the specific robot drive train. - static final double DRIVE_SPEED = 0.4; // Max driving speed for better distance accuracy. - static final double TURN_SPEED = 0.2; // Max turn speed to limit turn rate. - static final double HEADING_THRESHOLD = 1.0 ; // How close must the heading get to the target before moving to next step. - // Requiring more accuracy (a smaller number) will often make the turn take longer to get into the final position. - // Define the Proportional control coefficient (or GAIN) for "heading control". - // We define one value when Turning (larger errors), and the other is used when Driving straight (smaller errors). - // Increase these numbers if the heading does not correct strongly enough (eg: a heavy robot or using tracks) - // Decrease these numbers if the heading does not settle on the correct value (eg: very agile robot with omni wheels) - static final double P_TURN_GAIN = 0.02; // Larger is more responsive, but also less stable. - static final double P_DRIVE_GAIN = 0.03; // Larger is more responsive, but also less stable. - - - @Override - public void runOpMode() { - - // Initialize the drive system variables. - leftDrive = hardwareMap.get(DcMotor.class, "left_drive"); - rightDrive = hardwareMap.get(DcMotor.class, "right_drive"); - - // To drive forward, most robots need the motor on one side to be reversed, because the axles point in opposite directions. - // When run, this OpMode should start both motors driving forward. So adjust these two lines based on your first test drive. - // Note: The settings here assume direct drive on left and right wheels. Gear Reduction or 90 Deg drives may require direction flips - leftDrive.setDirection(DcMotor.Direction.REVERSE); - rightDrive.setDirection(DcMotor.Direction.FORWARD); - - /* The next two lines define Hub orientation. - * The Default Orientation (shown) is when a hub is mounted horizontally with the printed logo pointing UP and the USB port pointing FORWARD. - * - * To Do: EDIT these two lines to match YOUR mounting configuration. - */ - RevHubOrientationOnRobot.LogoFacingDirection logoDirection = RevHubOrientationOnRobot.LogoFacingDirection.UP; - RevHubOrientationOnRobot.UsbFacingDirection usbDirection = RevHubOrientationOnRobot.UsbFacingDirection.FORWARD; - RevHubOrientationOnRobot orientationOnRobot = new RevHubOrientationOnRobot(logoDirection, usbDirection); - - // Now initialize the IMU with this mounting orientation - // This sample expects the IMU to be in a REV Hub and named "imu". - imu = hardwareMap.get(IMU.class, "imu"); - imu.initialize(new IMU.Parameters(orientationOnRobot)); - - // Ensure the robot is stationary. Reset the encoders and set the motors to BRAKE mode - leftDrive.setMode(DcMotor.RunMode.STOP_AND_RESET_ENCODER); - rightDrive.setMode(DcMotor.RunMode.STOP_AND_RESET_ENCODER); - leftDrive.setZeroPowerBehavior(DcMotor.ZeroPowerBehavior.BRAKE); - rightDrive.setZeroPowerBehavior(DcMotor.ZeroPowerBehavior.BRAKE); - - // Wait for the game to start (Display Gyro value while waiting) - while (opModeInInit()) { - telemetry.addData(">", "Robot Heading = %4.0f", getHeading()); - telemetry.update(); - } - - // Set the encoders for closed loop speed control, and reset the heading. - leftDrive.setMode(DcMotor.RunMode.RUN_USING_ENCODER); - rightDrive.setMode(DcMotor.RunMode.RUN_USING_ENCODER); - imu.resetYaw(); - - // Step through each leg of the path, - // Notes: Reverse movement is obtained by setting a negative distance (not speed) - // holdHeading() is used after turns to let the heading stabilize - // Add a sleep(2000) after any step to keep the telemetry data visible for review - - driveStraight(DRIVE_SPEED, 24.0, 0.0); // Drive Forward 24" - turnToHeading( TURN_SPEED, -45.0); // Turn CW to -45 Degrees - holdHeading( TURN_SPEED, -45.0, 0.5); // Hold -45 Deg heading for a 1/2 second - - driveStraight(DRIVE_SPEED, 17.0, -45.0); // Drive Forward 17" at -45 degrees (12"x and 12"y) - turnToHeading( TURN_SPEED, 45.0); // Turn CCW to 45 Degrees - holdHeading( TURN_SPEED, 45.0, 0.5); // Hold 45 Deg heading for a 1/2 second - - driveStraight(DRIVE_SPEED, 17.0, 45.0); // Drive Forward 17" at 45 degrees (-12"x and 12"y) - turnToHeading( TURN_SPEED, 0.0); // Turn CW to 0 Degrees - holdHeading( TURN_SPEED, 0.0, 1.0); // Hold 0 Deg heading for 1 second - - driveStraight(DRIVE_SPEED,-48.0, 0.0); // Drive in Reverse 48" (should return to approx. staring position) - - telemetry.addData("Path", "Complete"); - telemetry.update(); - sleep(1000); // Pause to display last telemetry message. - } - - /* - * ==================================================================================================== - * Driving "Helper" functions are below this line. - * These provide the high and low level methods that handle driving straight and turning. - * ==================================================================================================== - */ - - // ********** HIGH Level driving functions. ******************** - - /** - * Drive in a straight line, on a fixed compass heading (angle), based on encoder counts. - * Move will stop if either of these conditions occur: - * 1) Move gets to the desired position - * 2) Driver stops the OpMode running. - * - * @param maxDriveSpeed MAX Speed for forward/rev motion (range 0 to +1.0) . - * @param distance Distance (in inches) to move from current position. Negative distance means move backward. - * @param heading Absolute Heading Angle (in Degrees) relative to last gyro reset. - * 0 = fwd. +ve is CCW from fwd. -ve is CW from forward. - * If a relative angle is required, add/subtract from the current robotHeading. - */ - public void driveStraight(double maxDriveSpeed, - double distance, - double heading) { - - // Ensure that the OpMode is still active - if (opModeIsActive()) { - - // Determine new target position, and pass to motor controller - int moveCounts = (int)(distance * COUNTS_PER_INCH); - leftTarget = leftDrive.getCurrentPosition() + moveCounts; - rightTarget = rightDrive.getCurrentPosition() + moveCounts; - - // Set Target FIRST, then turn on RUN_TO_POSITION - leftDrive.setTargetPosition(leftTarget); - rightDrive.setTargetPosition(rightTarget); - - leftDrive.setMode(DcMotor.RunMode.RUN_TO_POSITION); - rightDrive.setMode(DcMotor.RunMode.RUN_TO_POSITION); - - // Set the required driving speed (must be positive for RUN_TO_POSITION) - // Start driving straight, and then enter the control loop - maxDriveSpeed = Math.abs(maxDriveSpeed); - moveRobot(maxDriveSpeed, 0); - - // keep looping while we are still active, and BOTH motors are running. - while (opModeIsActive() && - (leftDrive.isBusy() && rightDrive.isBusy())) { - - // Determine required steering to keep on heading - turnSpeed = getSteeringCorrection(heading, P_DRIVE_GAIN); - - // if driving in reverse, the motor correction also needs to be reversed - if (distance < 0) - turnSpeed *= -1.0; - - // Apply the turning correction to the current driving speed. - moveRobot(driveSpeed, turnSpeed); - - // Display drive status for the driver. - sendTelemetry(true); - } - - // Stop all motion & Turn off RUN_TO_POSITION - moveRobot(0, 0); - leftDrive.setMode(DcMotor.RunMode.RUN_USING_ENCODER); - rightDrive.setMode(DcMotor.RunMode.RUN_USING_ENCODER); - } - } - - /** - * Spin on the central axis to point in a new direction. - *

- * Move will stop if either of these conditions occur: - *

- * 1) Move gets to the heading (angle) - *

- * 2) Driver stops the OpMode running. - * - * @param maxTurnSpeed Desired MAX speed of turn. (range 0 to +1.0) - * @param heading Absolute Heading Angle (in Degrees) relative to last gyro reset. - * 0 = fwd. +ve is CCW from fwd. -ve is CW from forward. - * If a relative angle is required, add/subtract from current heading. - */ - public void turnToHeading(double maxTurnSpeed, double heading) { - - // Run getSteeringCorrection() once to pre-calculate the current error - getSteeringCorrection(heading, P_DRIVE_GAIN); - - // keep looping while we are still active, and not on heading. - while (opModeIsActive() && (Math.abs(headingError) > HEADING_THRESHOLD)) { - - // Determine required steering to keep on heading - turnSpeed = getSteeringCorrection(heading, P_TURN_GAIN); - - // Clip the speed to the maximum permitted value. - turnSpeed = Range.clip(turnSpeed, -maxTurnSpeed, maxTurnSpeed); - - // Pivot in place by applying the turning correction - moveRobot(0, turnSpeed); - - // Display drive status for the driver. - sendTelemetry(false); - } - - // Stop all motion; - moveRobot(0, 0); - } - - /** - * Obtain & hold a heading for a finite amount of time - *

- * Move will stop once the requested time has elapsed - *

- * This function is useful for giving the robot a moment to stabilize its heading between movements. - * - * @param maxTurnSpeed Maximum differential turn speed (range 0 to +1.0) - * @param heading Absolute Heading Angle (in Degrees) relative to last gyro reset. - * 0 = fwd. +ve is CCW from fwd. -ve is CW from forward. - * If a relative angle is required, add/subtract from current heading. - * @param holdTime Length of time (in seconds) to hold the specified heading. - */ - public void holdHeading(double maxTurnSpeed, double heading, double holdTime) { - - ElapsedTime holdTimer = new ElapsedTime(); - holdTimer.reset(); - - // keep looping while we have time remaining. - while (opModeIsActive() && (holdTimer.time() < holdTime)) { - // Determine required steering to keep on heading - turnSpeed = getSteeringCorrection(heading, P_TURN_GAIN); - - // Clip the speed to the maximum permitted value. - turnSpeed = Range.clip(turnSpeed, -maxTurnSpeed, maxTurnSpeed); - - // Pivot in place by applying the turning correction - moveRobot(0, turnSpeed); - - // Display drive status for the driver. - sendTelemetry(false); - } - - // Stop all motion; - moveRobot(0, 0); - } - - // ********** LOW Level driving functions. ******************** - - /** - * Use a Proportional Controller to determine how much steering correction is required. - * - * @param desiredHeading The desired absolute heading (relative to last heading reset) - * @param proportionalGain Gain factor applied to heading error to obtain turning power. - * @return Turning power needed to get to required heading. - */ - public double getSteeringCorrection(double desiredHeading, double proportionalGain) { - targetHeading = desiredHeading; // Save for telemetry - - // Determine the heading current error - headingError = targetHeading - getHeading(); - - // Normalize the error to be within +/- 180 degrees - while (headingError > 180) headingError -= 360; - while (headingError <= -180) headingError += 360; - - // Multiply the error by the gain to determine the required steering correction/ Limit the result to +/- 1.0 - return Range.clip(headingError * proportionalGain, -1, 1); - } - - /** - * Take separate drive (fwd/rev) and turn (right/left) requests, - * combines them, and applies the appropriate speed commands to the left and right wheel motors. - * @param drive forward motor speed - * @param turn clockwise turning motor speed. - */ - public void moveRobot(double drive, double turn) { - driveSpeed = drive; // save this value as a class member so it can be used by telemetry. - turnSpeed = turn; // save this value as a class member so it can be used by telemetry. - - leftSpeed = drive - turn; - rightSpeed = drive + turn; - - // Scale speeds down if either one exceeds +/- 1.0; - double max = Math.max(Math.abs(leftSpeed), Math.abs(rightSpeed)); - if (max > 1.0) - { - leftSpeed /= max; - rightSpeed /= max; - } - - leftDrive.setPower(leftSpeed); - rightDrive.setPower(rightSpeed); - } - - /** - * Display the various control parameters while driving - * - * @param straight Set to true if we are driving straight, and the encoder positions should be included in the telemetry. - */ - private void sendTelemetry(boolean straight) { - - if (straight) { - telemetry.addData("Motion", "Drive Straight"); - telemetry.addData("Target Pos L:R", "%7d:%7d", leftTarget, rightTarget); - telemetry.addData("Actual Pos L:R", "%7d:%7d", leftDrive.getCurrentPosition(), - rightDrive.getCurrentPosition()); - } else { - telemetry.addData("Motion", "Turning"); - } - - telemetry.addData("Heading- Target : Current", "%5.2f : %5.0f", targetHeading, getHeading()); - telemetry.addData("Error : Steer Pwr", "%5.1f : %5.1f", headingError, turnSpeed); - telemetry.addData("Wheel Speeds L : R", "%5.2f : %5.2f", leftSpeed, rightSpeed); - telemetry.update(); - } - - /** - * read the Robot heading directly from the IMU (in degrees) - */ - public double getHeading() { - YawPitchRollAngles orientation = imu.getRobotYawPitchRollAngles(); - return orientation.getYaw(AngleUnit.DEGREES); - } -} diff --git a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/RobotAutoDriveByTime_Linear.java b/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/RobotAutoDriveByTime_Linear.java deleted file mode 100644 index a714748..0000000 --- a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/RobotAutoDriveByTime_Linear.java +++ /dev/null @@ -1,128 +0,0 @@ -/* Copyright (c) 2017 FIRST. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted (subject to the limitations in the disclaimer below) provided that - * the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list - * of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this - * list of conditions and the following disclaimer in the documentation and/or - * other materials provided with the distribution. - * - * Neither the name of FIRST nor the names of its contributors may be used to endorse or - * promote products derived from this software without specific prior written permission. - * - * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS - * LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.firstinspires.ftc.robotcontroller.external.samples; - -import com.qualcomm.robotcore.eventloop.opmode.Autonomous; -import com.qualcomm.robotcore.eventloop.opmode.Disabled; -import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode; -import com.qualcomm.robotcore.hardware.DcMotor; -import com.qualcomm.robotcore.util.ElapsedTime; - -/* - * This OpMode illustrates the concept of driving a path based on time. - * The code is structured as a LinearOpMode - * - * The code assumes that you do NOT have encoders on the wheels, - * otherwise you would use: RobotAutoDriveByEncoder; - * - * The desired path in this example is: - * - Drive forward for 3 seconds - * - Spin right for 1.3 seconds - * - Drive Backward for 1 Second - * - * The code is written in a simple form with no optimizations. - * However, there are several ways that this type of sequence could be streamlined, - * - * Use Android Studio to Copy this Class, and Paste it into your team's code folder with a new name. - * Remove or comment out the @Disabled line to add this OpMode to the Driver Station OpMode list - */ - -@Autonomous(name="Robot: Auto Drive By Time", group="Robot") -@Disabled -public class RobotAutoDriveByTime_Linear extends LinearOpMode { - - /* Declare OpMode members. */ - private DcMotor leftDrive = null; - private DcMotor rightDrive = null; - - private ElapsedTime runtime = new ElapsedTime(); - - - static final double FORWARD_SPEED = 0.6; - static final double TURN_SPEED = 0.5; - - @Override - public void runOpMode() { - - // Initialize the drive system variables. - leftDrive = hardwareMap.get(DcMotor.class, "left_drive"); - rightDrive = hardwareMap.get(DcMotor.class, "right_drive"); - - // To drive forward, most robots need the motor on one side to be reversed, because the axles point in opposite directions. - // When run, this OpMode should start both motors driving forward. So adjust these two lines based on your first test drive. - // Note: The settings here assume direct drive on left and right wheels. Gear Reduction or 90 Deg drives may require direction flips - leftDrive.setDirection(DcMotor.Direction.REVERSE); - rightDrive.setDirection(DcMotor.Direction.FORWARD); - - // Send telemetry message to signify robot waiting; - telemetry.addData("Status", "Ready to run"); // - telemetry.update(); - - // Wait for the game to start (driver presses START) - waitForStart(); - - // Step through each leg of the path, ensuring that the OpMode has not been stopped along the way. - - // Step 1: Drive forward for 3 seconds - leftDrive.setPower(FORWARD_SPEED); - rightDrive.setPower(FORWARD_SPEED); - runtime.reset(); - while (opModeIsActive() && (runtime.seconds() < 3.0)) { - telemetry.addData("Path", "Leg 1: %4.1f S Elapsed", runtime.seconds()); - telemetry.update(); - } - - // Step 2: Spin right for 1.3 seconds - leftDrive.setPower(TURN_SPEED); - rightDrive.setPower(-TURN_SPEED); - runtime.reset(); - while (opModeIsActive() && (runtime.seconds() < 1.3)) { - telemetry.addData("Path", "Leg 2: %4.1f S Elapsed", runtime.seconds()); - telemetry.update(); - } - - // Step 3: Drive Backward for 1 Second - leftDrive.setPower(-FORWARD_SPEED); - rightDrive.setPower(-FORWARD_SPEED); - runtime.reset(); - while (opModeIsActive() && (runtime.seconds() < 1.0)) { - telemetry.addData("Path", "Leg 3: %4.1f S Elapsed", runtime.seconds()); - telemetry.update(); - } - - // Step 4: Stop - leftDrive.setPower(0); - rightDrive.setPower(0); - - telemetry.addData("Path", "Complete"); - telemetry.update(); - sleep(1000); - } -} diff --git a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/RobotAutoDriveToAprilTagOmni.java b/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/RobotAutoDriveToAprilTagOmni.java deleted file mode 100644 index 9bac006..0000000 --- a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/RobotAutoDriveToAprilTagOmni.java +++ /dev/null @@ -1,321 +0,0 @@ -/* Copyright (c) 2023 FIRST. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted (subject to the limitations in the disclaimer below) provided that - * the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list - * of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this - * list of conditions and the following disclaimer in the documentation and/or - * other materials provided with the distribution. - * - * Neither the name of FIRST nor the names of its contributors may be used to endorse or - * promote products derived from this software without specific prior written permission. - * - * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS - * LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.firstinspires.ftc.robotcontroller.external.samples; - -import com.qualcomm.robotcore.eventloop.opmode.Disabled; -import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode; -import com.qualcomm.robotcore.eventloop.opmode.TeleOp; -import com.qualcomm.robotcore.hardware.DcMotor; -import com.qualcomm.robotcore.util.Range; -import org.firstinspires.ftc.robotcore.external.hardware.camera.BuiltinCameraDirection; -import org.firstinspires.ftc.robotcore.external.hardware.camera.WebcamName; -import org.firstinspires.ftc.robotcore.external.hardware.camera.controls.ExposureControl; -import org.firstinspires.ftc.robotcore.external.hardware.camera.controls.GainControl; -import org.firstinspires.ftc.vision.VisionPortal; -import org.firstinspires.ftc.vision.apriltag.AprilTagDetection; -import org.firstinspires.ftc.vision.apriltag.AprilTagProcessor; - -import java.util.List; -import java.util.concurrent.TimeUnit; - -/* - * This OpMode illustrates using a camera to locate and drive towards a specific AprilTag. - * The code assumes a Holonomic (Mecanum or X Drive) Robot. - * - * For an introduction to AprilTags, see the ftc-docs link below: - * https://ftc-docs.firstinspires.org/en/latest/apriltag/vision_portal/apriltag_intro/apriltag-intro.html - * - * When an AprilTag in the TagLibrary is detected, the SDK provides location and orientation of the tag, relative to the camera. - * This information is provided in the "ftcPose" member of the returned "detection", and is explained in the ftc-docs page linked below. - * https://ftc-docs.firstinspires.org/apriltag-detection-values - * - * The drive goal is to rotate to keep the Tag centered in the camera, while strafing to be directly in front of the tag, and - * driving towards the tag to achieve the desired distance. - * To reduce any motion blur (which will interrupt the detection process) the Camera exposure is reduced to a very low value (5mS) - * You can determine the best Exposure and Gain values by using the ConceptAprilTagOptimizeExposure OpMode in this Samples folder. - * - * The code assumes a Robot Configuration with motors named: leftfront_drive and rightfront_drive, leftback_drive and rightback_drive. - * The motor directions must be set so a positive power goes forward on all wheels. - * This sample assumes that the current game AprilTag Library (usually for the current season) is being loaded by default, - * so you should choose to approach a valid tag ID. - * - * Under manual control, the left stick will move forward/back & left/right. The right stick will rotate the robot. - * Manually drive the robot until it displays Target data on the Driver Station. - * - * Press and hold the *Left Bumper* to enable the automatic "Drive to target" mode. - * Release the Left Bumper to return to manual driving mode. - * - * Under "Drive To Target" mode, the robot has three goals: - * 1) Turn the robot to always keep the Tag centered on the camera frame. (Use the Target Bearing to turn the robot.) - * 2) Strafe the robot towards the centerline of the Tag, so it approaches directly in front of the tag. (Use the Target Yaw to strafe the robot) - * 3) Drive towards the Tag to get to the desired distance. (Use Tag Range to drive the robot forward/backward) - * - * Use DESIRED_DISTANCE to set how close you want the robot to get to the target. - * Speed and Turn sensitivity can be adjusted using the SPEED_GAIN, STRAFE_GAIN and TURN_GAIN constants. - * - * Use Android Studio to Copy this Class, and Paste it into the TeamCode/src/main/java/org/firstinspires/ftc/teamcode folder. - * Remove or comment out the @Disabled line to add this OpMode to the Driver Station OpMode list. - * - */ - -@TeleOp(name="Omni Drive To AprilTag", group = "Concept") -@Disabled -public class RobotAutoDriveToAprilTagOmni extends LinearOpMode -{ - // Adjust these numbers to suit your robot. - final double DESIRED_DISTANCE = 12.0; // this is how close the camera should get to the target (inches) - - // Set the GAIN constants to control the relationship between the measured position error, and how much power is - // applied to the drive motors to correct the error. - // Drive = Error * Gain Make these values smaller for smoother control, or larger for a more aggressive response. - final double SPEED_GAIN = 0.02 ; // Forward Speed Control "Gain". e.g. Ramp up to 50% power at a 25 inch error. (0.50 / 25.0) - final double STRAFE_GAIN = 0.015 ; // Strafe Speed Control "Gain". e.g. Ramp up to 37% power at a 25 degree Yaw error. (0.375 / 25.0) - final double TURN_GAIN = 0.01 ; // Turn Control "Gain". e.g. Ramp up to 25% power at a 25 degree error. (0.25 / 25.0) - - final double MAX_AUTO_SPEED = 0.5; // Clip the approach speed to this max value (adjust for your robot) - final double MAX_AUTO_STRAFE= 0.5; // Clip the strafing speed to this max value (adjust for your robot) - final double MAX_AUTO_TURN = 0.3; // Clip the turn speed to this max value (adjust for your robot) - - private DcMotor leftFrontDrive = null; // Used to control the left front drive wheel - private DcMotor rightFrontDrive = null; // Used to control the right front drive wheel - private DcMotor leftBackDrive = null; // Used to control the left back drive wheel - private DcMotor rightBackDrive = null; // Used to control the right back drive wheel - - private static final boolean USE_WEBCAM = true; // Set true to use a webcam, or false for a phone camera - private static final int DESIRED_TAG_ID = -1; // Choose the tag you want to approach or set to -1 for ANY tag. - private VisionPortal visionPortal; // Used to manage the video source. - private AprilTagProcessor aprilTag; // Used for managing the AprilTag detection process. - private AprilTagDetection desiredTag = null; // Used to hold the data for a detected AprilTag - - @Override public void runOpMode() - { - boolean targetFound = false; // Set to true when an AprilTag target is detected - double drive = 0; // Desired forward power/speed (-1 to +1) - double strafe = 0; // Desired strafe power/speed (-1 to +1) - double turn = 0; // Desired turning power/speed (-1 to +1) - - // Initialize the Apriltag Detection process - initAprilTag(); - - // Initialize the hardware variables. Note that the strings used here as parameters - // to 'get' must match the names assigned during the robot configuration. - // step (using the FTC Robot Controller app on the phone). - leftFrontDrive = hardwareMap.get(DcMotor.class, "leftfront_drive"); - rightFrontDrive = hardwareMap.get(DcMotor.class, "rightfront_drive"); - leftBackDrive = hardwareMap.get(DcMotor.class, "leftback_drive"); - rightBackDrive = hardwareMap.get(DcMotor.class, "rightback_drive"); - - // To drive forward, most robots need the motor on one side to be reversed, because the axles point in opposite directions. - // When run, this OpMode should start both motors driving forward. So adjust these two lines based on your first test drive. - // Note: The settings here assume direct drive on left and right wheels. Gear Reduction or 90 Deg drives may require direction flips - leftFrontDrive.setDirection(DcMotor.Direction.REVERSE); - leftBackDrive.setDirection(DcMotor.Direction.REVERSE); - rightFrontDrive.setDirection(DcMotor.Direction.FORWARD); - rightBackDrive.setDirection(DcMotor.Direction.FORWARD); - - if (USE_WEBCAM) - setManualExposure(6, 250); // Use low exposure time to reduce motion blur - - // Wait for driver to press start - telemetry.addData("Camera preview on/off", "3 dots, Camera Stream"); - telemetry.addData(">", "Touch START to start OpMode"); - telemetry.update(); - waitForStart(); - - while (opModeIsActive()) - { - targetFound = false; - desiredTag = null; - - // Step through the list of detected tags and look for a matching tag - List currentDetections = aprilTag.getDetections(); - for (AprilTagDetection detection : currentDetections) { - // Look to see if we have size info on this tag. - if (detection.metadata != null) { - // Check to see if we want to track towards this tag. - if ((DESIRED_TAG_ID < 0) || (detection.id == DESIRED_TAG_ID)) { - // Yes, we want to use this tag. - targetFound = true; - desiredTag = detection; - break; // don't look any further. - } else { - // This tag is in the library, but we do not want to track it right now. - telemetry.addData("Skipping", "Tag ID %d is not desired", detection.id); - } - } else { - // This tag is NOT in the library, so we don't have enough information to track to it. - telemetry.addData("Unknown", "Tag ID %d is not in TagLibrary", detection.id); - } - } - - // Tell the driver what we see, and what to do. - if (targetFound) { - telemetry.addData("\n>","HOLD Left-Bumper to Drive to Target\n"); - telemetry.addData("Found", "ID %d (%s)", desiredTag.id, desiredTag.metadata.name); - telemetry.addData("Range", "%5.1f inches", desiredTag.ftcPose.range); - telemetry.addData("Bearing","%3.0f degrees", desiredTag.ftcPose.bearing); - telemetry.addData("Yaw","%3.0f degrees", desiredTag.ftcPose.yaw); - } else { - telemetry.addData("\n>","Drive using joysticks to find valid target\n"); - } - - // If Left Bumper is being pressed, AND we have found the desired target, Drive to target Automatically . - if (gamepad1.left_bumper && targetFound) { - - // Determine heading, range and Yaw (tag image rotation) error so we can use them to control the robot automatically. - double rangeError = (desiredTag.ftcPose.range - DESIRED_DISTANCE); - double headingError = desiredTag.ftcPose.bearing; - double yawError = desiredTag.ftcPose.yaw; - - // Use the speed and turn "gains" to calculate how we want the robot to move. - drive = Range.clip(rangeError * SPEED_GAIN, -MAX_AUTO_SPEED, MAX_AUTO_SPEED); - turn = Range.clip(headingError * TURN_GAIN, -MAX_AUTO_TURN, MAX_AUTO_TURN) ; - strafe = Range.clip(-yawError * STRAFE_GAIN, -MAX_AUTO_STRAFE, MAX_AUTO_STRAFE); - - telemetry.addData("Auto","Drive %5.2f, Strafe %5.2f, Turn %5.2f ", drive, strafe, turn); - } else { - - // drive using manual POV Joystick mode. Slow things down to make the robot more controlable. - drive = -gamepad1.left_stick_y / 2.0; // Reduce drive rate to 50%. - strafe = -gamepad1.left_stick_x / 2.0; // Reduce strafe rate to 50%. - turn = -gamepad1.right_stick_x / 3.0; // Reduce turn rate to 33%. - telemetry.addData("Manual","Drive %5.2f, Strafe %5.2f, Turn %5.2f ", drive, strafe, turn); - } - telemetry.update(); - - // Apply desired axes motions to the drivetrain. - moveRobot(drive, strafe, turn); - sleep(10); - } - } - - /** - * Move robot according to desired axes motions - *

- * Positive X is forward - *

- * Positive Y is strafe left - *

- * Positive Yaw is counter-clockwise - */ - public void moveRobot(double x, double y, double yaw) { - // Calculate wheel powers. - double leftFrontPower = x -y -yaw; - double rightFrontPower = x +y +yaw; - double leftBackPower = x +y -yaw; - double rightBackPower = x -y +yaw; - - // Normalize wheel powers to be less than 1.0 - double max = Math.max(Math.abs(leftFrontPower), Math.abs(rightFrontPower)); - max = Math.max(max, Math.abs(leftBackPower)); - max = Math.max(max, Math.abs(rightBackPower)); - - if (max > 1.0) { - leftFrontPower /= max; - rightFrontPower /= max; - leftBackPower /= max; - rightBackPower /= max; - } - - // Send powers to the wheels. - leftFrontDrive.setPower(leftFrontPower); - rightFrontDrive.setPower(rightFrontPower); - leftBackDrive.setPower(leftBackPower); - rightBackDrive.setPower(rightBackPower); - } - - /** - * Initialize the AprilTag processor. - */ - private void initAprilTag() { - // Create the AprilTag processor by using a builder. - aprilTag = new AprilTagProcessor.Builder().build(); - - // Adjust Image Decimation to trade-off detection-range for detection-rate. - // e.g. Some typical detection data using a Logitech C920 WebCam - // Decimation = 1 .. Detect 2" Tag from 10 feet away at 10 Frames per second - // Decimation = 2 .. Detect 2" Tag from 6 feet away at 22 Frames per second - // Decimation = 3 .. Detect 2" Tag from 4 feet away at 30 Frames Per Second - // Decimation = 3 .. Detect 5" Tag from 10 feet away at 30 Frames Per Second - // Note: Decimation can be changed on-the-fly to adapt during a match. - aprilTag.setDecimation(2); - - // Create the vision portal by using a builder. - if (USE_WEBCAM) { - visionPortal = new VisionPortal.Builder() - .setCamera(hardwareMap.get(WebcamName.class, "Webcam 1")) - .addProcessor(aprilTag) - .build(); - } else { - visionPortal = new VisionPortal.Builder() - .setCamera(BuiltinCameraDirection.BACK) - .addProcessor(aprilTag) - .build(); - } - } - - /* - Manually set the camera gain and exposure. - This can only be called AFTER calling initAprilTag(), and only works for Webcams; - */ - private void setManualExposure(int exposureMS, int gain) { - // Wait for the camera to be open, then use the controls - - if (visionPortal == null) { - return; - } - - // Make sure camera is streaming before we try to set the exposure controls - if (visionPortal.getCameraState() != VisionPortal.CameraState.STREAMING) { - telemetry.addData("Camera", "Waiting"); - telemetry.update(); - while (!isStopRequested() && (visionPortal.getCameraState() != VisionPortal.CameraState.STREAMING)) { - sleep(20); - } - telemetry.addData("Camera", "Ready"); - telemetry.update(); - } - - // Set camera controls unless we are stopping. - if (!isStopRequested()) - { - ExposureControl exposureControl = visionPortal.getCameraControl(ExposureControl.class); - if (exposureControl.getMode() != ExposureControl.Mode.Manual) { - exposureControl.setMode(ExposureControl.Mode.Manual); - sleep(50); - } - exposureControl.setExposure((long)exposureMS, TimeUnit.MILLISECONDS); - sleep(20); - GainControl gainControl = visionPortal.getCameraControl(GainControl.class); - gainControl.setGain(gain); - sleep(20); - } - } -} diff --git a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/RobotAutoDriveToAprilTagTank.java b/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/RobotAutoDriveToAprilTagTank.java deleted file mode 100644 index ba3eb4f..0000000 --- a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/RobotAutoDriveToAprilTagTank.java +++ /dev/null @@ -1,298 +0,0 @@ -/* Copyright (c) 2023 FIRST. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted (subject to the limitations in the disclaimer below) provided that - * the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list - * of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this - * list of conditions and the following disclaimer in the documentation and/or - * other materials provided with the distribution. - * - * Neither the name of FIRST nor the names of its contributors may be used to endorse or - * promote products derived from this software without specific prior written permission. - * - * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS - * LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.firstinspires.ftc.robotcontroller.external.samples; - -import com.qualcomm.robotcore.eventloop.opmode.Disabled; -import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode; -import com.qualcomm.robotcore.eventloop.opmode.TeleOp; -import com.qualcomm.robotcore.hardware.DcMotor; -import com.qualcomm.robotcore.util.Range; -import org.firstinspires.ftc.robotcore.external.hardware.camera.BuiltinCameraDirection; -import org.firstinspires.ftc.robotcore.external.hardware.camera.WebcamName; -import org.firstinspires.ftc.robotcore.external.hardware.camera.controls.ExposureControl; -import org.firstinspires.ftc.robotcore.external.hardware.camera.controls.GainControl; -import org.firstinspires.ftc.vision.VisionPortal; -import org.firstinspires.ftc.vision.apriltag.AprilTagDetection; -import org.firstinspires.ftc.vision.apriltag.AprilTagProcessor; - -import java.util.List; -import java.util.concurrent.TimeUnit; - -/* - * This OpMode illustrates using a camera to locate and drive towards a specific AprilTag. - * The code assumes a basic two-wheel (Tank) Robot Drivetrain - * - * For an introduction to AprilTags, see the ftc-docs link below: - * https://ftc-docs.firstinspires.org/en/latest/apriltag/vision_portal/apriltag_intro/apriltag-intro.html - * - * When an AprilTag in the TagLibrary is detected, the SDK provides location and orientation of the tag, relative to the camera. - * This information is provided in the "ftcPose" member of the returned "detection", and is explained in the ftc-docs page linked below. - * https://ftc-docs.firstinspires.org/apriltag-detection-values - * - * The driving goal is to rotate to keep the tag centered in the camera, while driving towards the tag to achieve the desired distance. - * To reduce any motion blur (which will interrupt the detection process) the Camera exposure is reduced to a very low value (5mS) - * You can determine the best exposure and gain values by using the ConceptAprilTagOptimizeExposure OpMode in this Samples folder. - * - * The code assumes a Robot Configuration with motors named left_drive and right_drive. - * The motor directions must be set so a positive power goes forward on both wheels; - * This sample assumes that the default AprilTag Library (usually for the current season) is being loaded by default - * so you should choose to approach a valid tag ID. - * - * Under manual control, the left stick will move forward/back, and the right stick will rotate the robot. - * This is called POV Joystick mode, different than Tank Drive (where each joystick controls a wheel). - * - * Manually drive the robot until it displays Target data on the Driver Station. - * Press and hold the *Left Bumper* to enable the automatic "Drive to target" mode. - * Release the Left Bumper to return to manual driving mode. - * - * Under "Drive To Target" mode, the robot has two goals: - * 1) Turn the robot to always keep the Tag centered on the camera frame. (Use the Target Bearing to turn the robot.) - * 2) Drive towards the Tag to get to the desired distance. (Use Tag Range to drive the robot forward/backward) - * - * Use DESIRED_DISTANCE to set how close you want the robot to get to the target. - * Speed and Turn sensitivity can be adjusted using the SPEED_GAIN and TURN_GAIN constants. - * - * Use Android Studio to Copy this Class, and Paste it into the TeamCode/src/main/java/org/firstinspires/ftc/teamcode folder. - * Remove or comment out the @Disabled line to add this OpMode to the Driver Station OpMode list. - * - */ - -@TeleOp(name="Tank Drive To AprilTag", group = "Concept") -@Disabled -public class RobotAutoDriveToAprilTagTank extends LinearOpMode -{ - // Adjust these numbers to suit your robot. - final double DESIRED_DISTANCE = 12.0; // this is how close the camera should get to the target (inches) - - // Set the GAIN constants to control the relationship between the measured position error, and how much power is - // applied to the drive motors to correct the error. - // Drive = Error * Gain Make these values smaller for smoother control, or larger for a more aggressive response. - final double SPEED_GAIN = 0.02 ; // Speed Control "Gain". e.g. Ramp up to 50% power at a 25 inch error. (0.50 / 25.0) - final double TURN_GAIN = 0.01 ; // Turn Control "Gain". e.g. Ramp up to 25% power at a 25 degree error. (0.25 / 25.0) - - final double MAX_AUTO_SPEED = 0.5; // Clip the approach speed to this max value (adjust for your robot) - final double MAX_AUTO_TURN = 0.25; // Clip the turn speed to this max value (adjust for your robot) - - private DcMotor leftDrive = null; // Used to control the left drive wheel - private DcMotor rightDrive = null; // Used to control the right drive wheel - - private static final boolean USE_WEBCAM = true; // Set true to use a webcam, or false for a phone camera - private static final int DESIRED_TAG_ID = -1; // Choose the tag you want to approach or set to -1 for ANY tag. - private VisionPortal visionPortal; // Used to manage the video source. - private AprilTagProcessor aprilTag; // Used for managing the AprilTag detection process. - private AprilTagDetection desiredTag = null; // Used to hold the data for a detected AprilTag - - @Override public void runOpMode() - { - boolean targetFound = false; // Set to true when an AprilTag target is detected - double drive = 0; // Desired forward power/speed (-1 to +1) +ve is forward - double turn = 0; // Desired turning power/speed (-1 to +1) +ve is CounterClockwise - - // Initialize the Apriltag Detection process - initAprilTag(); - - // Initialize the hardware variables. Note that the strings used here as parameters - // to 'get' must match the names assigned during the robot configuration. - // step (using the FTC Robot Controller app on the phone). - leftDrive = hardwareMap.get(DcMotor.class, "left_drive"); - rightDrive = hardwareMap.get(DcMotor.class, "right_drive"); - - // To drive forward, most robots need the motor on one side to be reversed because the axles point in opposite directions. - // When run, this OpMode should start both motors driving forward. So adjust these two lines based on your first test drive. - // Note: The settings here assume direct drive on left and right wheels. Single Gear Reduction or 90 Deg drives may require direction flips - leftDrive.setDirection(DcMotor.Direction.REVERSE); - rightDrive.setDirection(DcMotor.Direction.FORWARD); - - if (USE_WEBCAM) - setManualExposure(6, 250); // Use low exposure time to reduce motion blur - - // Wait for the driver to press Start - telemetry.addData("Camera preview on/off", "3 dots, Camera Stream"); - telemetry.addData(">", "Touch START to start OpMode"); - telemetry.update(); - waitForStart(); - - while (opModeIsActive()) - { - targetFound = false; - desiredTag = null; - - // Step through the list of detected tags and look for a matching tag - List currentDetections = aprilTag.getDetections(); - for (AprilTagDetection detection : currentDetections) { - // Look to see if we have size info on this tag. - if (detection.metadata != null) { - // Check to see if we want to track towards this tag. - if ((DESIRED_TAG_ID < 0) || (detection.id == DESIRED_TAG_ID)) { - // Yes, we want to use this tag. - targetFound = true; - desiredTag = detection; - break; // don't look any further. - } else { - // This tag is in the library, but we do not want to track it right now. - telemetry.addData("Skipping", "Tag ID %d is not desired", detection.id); - } - } else { - // This tag is NOT in the library, so we don't have enough information to track to it. - telemetry.addData("Unknown", "Tag ID %d is not in TagLibrary", detection.id); - } - } - - // Tell the driver what we see, and what to do. - if (targetFound) { - telemetry.addData("\n>","HOLD Left-Bumper to Drive to Target\n"); - telemetry.addData("Found", "ID %d (%s)", desiredTag.id, desiredTag.metadata.name); - telemetry.addData("Range", "%5.1f inches", desiredTag.ftcPose.range); - telemetry.addData("Bearing","%3.0f degrees", desiredTag.ftcPose.bearing); - } else { - telemetry.addData("\n>","Drive using joysticks to find valid target\n"); - } - - // If Left Bumper is being pressed, AND we have found the desired target, Drive to target Automatically . - if (gamepad1.left_bumper && targetFound) { - - // Determine heading and range error so we can use them to control the robot automatically. - double rangeError = (desiredTag.ftcPose.range - DESIRED_DISTANCE); - double headingError = desiredTag.ftcPose.bearing; - - // Use the speed and turn "gains" to calculate how we want the robot to move. Clip it to the maximum - drive = Range.clip(rangeError * SPEED_GAIN, -MAX_AUTO_SPEED, MAX_AUTO_SPEED); - turn = Range.clip(headingError * TURN_GAIN, -MAX_AUTO_TURN, MAX_AUTO_TURN) ; - - telemetry.addData("Auto","Drive %5.2f, Turn %5.2f", drive, turn); - } else { - - // drive using manual POV Joystick mode. - drive = -gamepad1.left_stick_y / 2.0; // Reduce drive rate to 50%. - turn = -gamepad1.right_stick_x / 4.0; // Reduce turn rate to 25%. - telemetry.addData("Manual","Drive %5.2f, Turn %5.2f", drive, turn); - } - telemetry.update(); - - // Apply desired axes motions to the drivetrain. - moveRobot(drive, turn); - sleep(10); - } - } - - /** - * Move robot according to desired axes motions - *

- * Positive X is forward - *

- * Positive Yaw is counter-clockwise - */ - public void moveRobot(double x, double yaw) { - // Calculate left and right wheel powers. - double leftPower = x - yaw; - double rightPower = x + yaw; - - // Normalize wheel powers to be less than 1.0 - double max = Math.max(Math.abs(leftPower), Math.abs(rightPower)); - if (max >1.0) { - leftPower /= max; - rightPower /= max; - } - - // Send powers to the wheels. - leftDrive.setPower(leftPower); - rightDrive.setPower(rightPower); - } - - /** - * Initialize the AprilTag processor. - */ - private void initAprilTag() { - // Create the AprilTag processor by using a builder. - aprilTag = new AprilTagProcessor.Builder().build(); - - // Adjust Image Decimation to trade-off detection-range for detection-rate. - // e.g. Some typical detection data using a Logitech C920 WebCam - // Decimation = 1 .. Detect 2" Tag from 10 feet away at 10 Frames per second - // Decimation = 2 .. Detect 2" Tag from 6 feet away at 22 Frames per second - // Decimation = 3 .. Detect 2" Tag from 4 feet away at 30 Frames Per Second - // Decimation = 3 .. Detect 5" Tag from 10 feet away at 30 Frames Per Second - // Note: Decimation can be changed on-the-fly to adapt during a match. - aprilTag.setDecimation(2); - - // Create the vision portal by using a builder. - if (USE_WEBCAM) { - visionPortal = new VisionPortal.Builder() - .setCamera(hardwareMap.get(WebcamName.class, "Webcam 1")) - .addProcessor(aprilTag) - .build(); - } else { - visionPortal = new VisionPortal.Builder() - .setCamera(BuiltinCameraDirection.BACK) - .addProcessor(aprilTag) - .build(); - } - } - - /* - Manually set the camera gain and exposure. - This can only be called AFTER calling initAprilTag(), and only works for Webcams; - */ - private void setManualExposure(int exposureMS, int gain) { - // Wait for the camera to be open, then use the controls - - if (visionPortal == null) { - return; - } - - // Make sure camera is streaming before we try to set the exposure controls - if (visionPortal.getCameraState() != VisionPortal.CameraState.STREAMING) { - telemetry.addData("Camera", "Waiting"); - telemetry.update(); - while (!isStopRequested() && (visionPortal.getCameraState() != VisionPortal.CameraState.STREAMING)) { - sleep(20); - } - telemetry.addData("Camera", "Ready"); - telemetry.update(); - } - - // Set camera controls unless we are stopping. - if (!isStopRequested()) - { - ExposureControl exposureControl = visionPortal.getCameraControl(ExposureControl.class); - if (exposureControl.getMode() != ExposureControl.Mode.Manual) { - exposureControl.setMode(ExposureControl.Mode.Manual); - sleep(50); - } - exposureControl.setExposure((long)exposureMS, TimeUnit.MILLISECONDS); - sleep(20); - GainControl gainControl = visionPortal.getCameraControl(GainControl.class); - gainControl.setGain(gain); - sleep(20); - telemetry.addData("Camera", "Ready"); - telemetry.update(); - } - } -} diff --git a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/RobotAutoDriveToLine_Linear.java b/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/RobotAutoDriveToLine_Linear.java deleted file mode 100644 index 780f260..0000000 --- a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/RobotAutoDriveToLine_Linear.java +++ /dev/null @@ -1,142 +0,0 @@ -/* Copyright (c) 2017 FIRST. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted (subject to the limitations in the disclaimer below) provided that - * the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list - * of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this - * list of conditions and the following disclaimer in the documentation and/or - * other materials provided with the distribution. - * - * Neither the name of FIRST nor the names of its contributors may be used to endorse or - * promote products derived from this software without specific prior written permission. - * - * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS - * LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.firstinspires.ftc.robotcontroller.external.samples; - -import com.qualcomm.robotcore.eventloop.opmode.Autonomous; -import com.qualcomm.robotcore.eventloop.opmode.Disabled; -import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode; -import com.qualcomm.robotcore.hardware.DcMotor; -import com.qualcomm.robotcore.hardware.NormalizedColorSensor; -import com.qualcomm.robotcore.hardware.NormalizedRGBA; -import com.qualcomm.robotcore.hardware.SwitchableLight; - -/* - * This OpMode illustrates the concept of driving up to a line and then stopping. - * The code is structured as a LinearOpMode - * - * The Sensor used here can be a REV Color Sensor V2 or V3. Make sure the white LED is turned on. - * The sensor can be plugged into any I2C port, and must be named "sensor_color" in the active configuration. - * - * Depending on the height of your color sensor, you may want to set the sensor "gain". - * The higher the gain, the greater the reflected light reading will be. - * Use the SensorColor sample in this folder to determine the minimum gain value that provides an - * "Alpha" reading of 1.0 when you are on top of the white line. In this sample, we use a gain of 15 - * which works well with a Rev V2 color sensor - * - * Setting the correct WHITE_THRESHOLD value is key to stopping correctly. - * This should be set halfway between the bare-tile, and white-line "Alpha" values. - * The reflected light value can be read on the screen once the OpMode has been INIT, but before it is STARTED. - * Move the sensor on and off the white line and note the min and max readings. - * Edit this code to make WHITE_THRESHOLD halfway between the min and max. - * - * Use Android Studio to Copy this Class, and Paste it into your team's code folder with a new name. - * Remove or comment out the @Disabled line to add this OpMode to the Driver Station OpMode list - */ - -@Autonomous(name="Robot: Auto Drive To Line", group="Robot") -@Disabled -public class RobotAutoDriveToLine_Linear extends LinearOpMode { - - /* Declare OpMode members. */ - private DcMotor leftDrive = null; - private DcMotor rightDrive = null; - - /** The variable to store a reference to our color sensor hardware object */ - NormalizedColorSensor colorSensor; - - static final double WHITE_THRESHOLD = 0.5; // spans between 0.0 - 1.0 from dark to light - static final double APPROACH_SPEED = 0.25; - - @Override - public void runOpMode() { - - // Initialize the drive system variables. - leftDrive = hardwareMap.get(DcMotor.class, "left_drive"); - rightDrive = hardwareMap.get(DcMotor.class, "right_drive"); - - // To drive forward, most robots need the motor on one side to be reversed, because the axles point in opposite directions. - // When run, this OpMode should start both motors driving forward. So adjust these two lines based on your first test drive. - // Note: The settings here assume direct drive on left and right wheels. Gear Reduction or 90 Deg drives may require direction flips - leftDrive.setDirection(DcMotor.Direction.REVERSE); - rightDrive.setDirection(DcMotor.Direction.FORWARD); - - // If there are encoders connected, switch to RUN_USING_ENCODER mode for greater accuracy - // leftDrive.setMode(DcMotor.RunMode.RUN_USING_ENCODER); - // rightDrive.setMode(DcMotor.RunMode.RUN_USING_ENCODER); - - // Get a reference to our sensor object. It's recommended to use NormalizedColorSensor over - // ColorSensor, because NormalizedColorSensor consistently gives values between 0 and 1, while - // the values you get from ColorSensor are dependent on the specific sensor you're using. - colorSensor = hardwareMap.get(NormalizedColorSensor.class, "sensor_color"); - - // If necessary, turn ON the white LED (if there is no LED switch on the sensor) - if (colorSensor instanceof SwitchableLight) { - ((SwitchableLight)colorSensor).enableLight(true); - } - - // Some sensors allow you to set your light sensor gain for optimal sensitivity... - // See the SensorColor sample in this folder for how to determine the optimal gain. - // A gain of 15 causes a Rev Color Sensor V2 to produce an Alpha value of 1.0 at about 1.5" above the floor. - colorSensor.setGain(15); - - // Wait for driver to press START) - // Abort this loop is started or stopped. - while (opModeInInit()) { - - // Send telemetry message to signify robot waiting; - telemetry.addData("Status", "Ready to drive to white line."); // - - // Display the light level while we are waiting to start - getBrightness(); - } - - // Start the robot moving forward, and then begin looking for a white line. - leftDrive.setPower(APPROACH_SPEED); - rightDrive.setPower(APPROACH_SPEED); - - // run until the white line is seen OR the driver presses STOP; - while (opModeIsActive() && (getBrightness() < WHITE_THRESHOLD)) { - sleep(5); - } - - // Stop all motors - leftDrive.setPower(0); - rightDrive.setPower(0); - } - - // to obtain reflected light, read the normalized values from the color sensor. Return the Alpha channel. - double getBrightness() { - NormalizedRGBA colors = colorSensor.getNormalizedColors(); - telemetry.addData("Light Level (0 to 1)", "%4.2f", colors.alpha); - telemetry.update(); - - return colors.alpha; - } -} diff --git a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/RobotHardware.java b/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/RobotHardware.java deleted file mode 100644 index b1c8de6..0000000 --- a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/RobotHardware.java +++ /dev/null @@ -1,167 +0,0 @@ -/* Copyright (c) 2022 FIRST. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted (subject to the limitations in the disclaimer below) provided that - * the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list - * of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this - * list of conditions and the following disclaimer in the documentation and/or - * other materials provided with the distribution. - * - * Neither the name of FIRST nor the names of its contributors may be used to endorse or - * promote products derived from this software without specific prior written permission. - * - * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS - * LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.firstinspires.ftc.robotcontroller.external.samples; - -import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode; -import com.qualcomm.robotcore.hardware.DcMotor; -import com.qualcomm.robotcore.hardware.Servo; -import com.qualcomm.robotcore.util.Range; - -/* - * This file works in conjunction with the External Hardware Class sample called: ConceptExternalHardwareClass.java - * Please read the explanations in that Sample about how to use this class definition. - * - * This file defines a Java Class that performs all the setup and configuration for a sample robot's hardware (motors and sensors). - * It assumes three motors (left_drive, right_drive and arm) and two servos (left_hand and right_hand) - * - * This one file/class can be used by ALL of your OpModes without having to cut & paste the code each time. - * - * Where possible, the actual hardware objects are "abstracted" (or hidden) so the OpMode code just makes calls into the class, - * rather than accessing the internal hardware directly. This is why the objects are declared "private". - * - * Use Android Studio to Copy this Class, and Paste it into your team's code folder with *exactly the same name*. - * - * Or... In OnBot Java, add a new file named RobotHardware.java, select this sample, and select Not an OpMode. - * Also add a new OpMode, select the sample ConceptExternalHardwareClass.java, and select TeleOp. - * - */ - -public class RobotHardware { - - /* Declare OpMode members. */ - private LinearOpMode myOpMode = null; // gain access to methods in the calling OpMode. - - // Define Motor and Servo objects (Make them private so they can't be accessed externally) - private DcMotor leftDrive = null; - private DcMotor rightDrive = null; - private DcMotor armMotor = null; - private Servo leftHand = null; - private Servo rightHand = null; - - // Define Drive constants. Make them public so they CAN be used by the calling OpMode - public static final double MID_SERVO = 0.5 ; - public static final double HAND_SPEED = 0.02 ; // sets rate to move servo - public static final double ARM_UP_POWER = 0.45 ; - public static final double ARM_DOWN_POWER = -0.45 ; - - // Define a constructor that allows the OpMode to pass a reference to itself. - public RobotHardware (LinearOpMode opmode) { - myOpMode = opmode; - } - - /** - * Initialize all the robot's hardware. - * This method must be called ONCE when the OpMode is initialized. - *

- * All of the hardware devices are accessed via the hardware map, and initialized. - */ - public void init() { - // Define and Initialize Motors (note: need to use reference to actual OpMode). - leftDrive = myOpMode.hardwareMap.get(DcMotor.class, "left_drive"); - rightDrive = myOpMode.hardwareMap.get(DcMotor.class, "right_drive"); - armMotor = myOpMode.hardwareMap.get(DcMotor.class, "arm"); - - // To drive forward, most robots need the motor on one side to be reversed, because the axles point in opposite directions. - // Pushing the left stick forward MUST make robot go forward. So adjust these two lines based on your first test drive. - // Note: The settings here assume direct drive on left and right wheels. Gear Reduction or 90 Deg drives may require direction flips - leftDrive.setDirection(DcMotor.Direction.REVERSE); - rightDrive.setDirection(DcMotor.Direction.FORWARD); - - // If there are encoders connected, switch to RUN_USING_ENCODER mode for greater accuracy - // leftDrive.setMode(DcMotor.RunMode.RUN_USING_ENCODER); - // rightDrive.setMode(DcMotor.RunMode.RUN_USING_ENCODER); - - // Define and initialize ALL installed servos. - leftHand = myOpMode.hardwareMap.get(Servo.class, "left_hand"); - rightHand = myOpMode.hardwareMap.get(Servo.class, "right_hand"); - leftHand.setPosition(MID_SERVO); - rightHand.setPosition(MID_SERVO); - - myOpMode.telemetry.addData(">", "Hardware Initialized"); - myOpMode.telemetry.update(); - } - - /** - * Calculates the left/right motor powers required to achieve the requested - * robot motions: Drive (Axial motion) and Turn (Yaw motion). - * Then sends these power levels to the motors. - * - * @param Drive Fwd/Rev driving power (-1.0 to 1.0) +ve is forward - * @param Turn Right/Left turning power (-1.0 to 1.0) +ve is CW - */ - public void driveRobot(double Drive, double Turn) { - // Combine drive and turn for blended motion. - double left = Drive + Turn; - double right = Drive - Turn; - - // Scale the values so neither exceed +/- 1.0 - double max = Math.max(Math.abs(left), Math.abs(right)); - if (max > 1.0) - { - left /= max; - right /= max; - } - - // Use existing function to drive both wheels. - setDrivePower(left, right); - } - - /** - * Pass the requested wheel motor powers to the appropriate hardware drive motors. - * - * @param leftWheel Fwd/Rev driving power (-1.0 to 1.0) +ve is forward - * @param rightWheel Fwd/Rev driving power (-1.0 to 1.0) +ve is forward - */ - public void setDrivePower(double leftWheel, double rightWheel) { - // Output the values to the motor drives. - leftDrive.setPower(leftWheel); - rightDrive.setPower(rightWheel); - } - - /** - * Pass the requested arm power to the appropriate hardware drive motor - * - * @param power driving power (-1.0 to 1.0) - */ - public void setArmPower(double power) { - armMotor.setPower(power); - } - - /** - * Send the two hand-servos to opposing (mirrored) positions, based on the passed offset. - * - * @param offset - */ - public void setHandPositions(double offset) { - offset = Range.clip(offset, -0.5, 0.5); - leftHand.setPosition(MID_SERVO + offset); - rightHand.setPosition(MID_SERVO - offset); - } -} diff --git a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/RobotTeleopPOV_Linear.java b/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/RobotTeleopPOV_Linear.java deleted file mode 100644 index af3840d..0000000 --- a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/RobotTeleopPOV_Linear.java +++ /dev/null @@ -1,159 +0,0 @@ -/* Copyright (c) 2017 FIRST. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted (subject to the limitations in the disclaimer below) provided that - * the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list - * of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this - * list of conditions and the following disclaimer in the documentation and/or - * other materials provided with the distribution. - * - * Neither the name of FIRST nor the names of its contributors may be used to endorse or - * promote products derived from this software without specific prior written permission. - * - * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS - * LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.firstinspires.ftc.robotcontroller.external.samples; - -import com.qualcomm.robotcore.eventloop.opmode.Disabled; -import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode; -import com.qualcomm.robotcore.eventloop.opmode.TeleOp; -import com.qualcomm.robotcore.hardware.DcMotor; -import com.qualcomm.robotcore.hardware.Servo; -import com.qualcomm.robotcore.util.Range; - -/* - * This OpMode executes a POV Game style Teleop for a direct drive robot - * The code is structured as a LinearOpMode - * - * In this mode the left stick moves the robot FWD and back, the Right stick turns left and right. - * It raises and lowers the arm using the Gamepad Y and A buttons respectively. - * It also opens and closes the claws slowly using the left and right Bumper buttons. - * - * Use Android Studio to Copy this Class, and Paste it into your team's code folder with a new name. - * Remove or comment out the @Disabled line to add this OpMode to the Driver Station OpMode list - */ - -@TeleOp(name="Robot: Teleop POV", group="Robot") -@Disabled -public class RobotTeleopPOV_Linear extends LinearOpMode { - - /* Declare OpMode members. */ - public DcMotor leftDrive = null; - public DcMotor rightDrive = null; - public DcMotor leftArm = null; - public Servo leftClaw = null; - public Servo rightClaw = null; - - double clawOffset = 0; - - public static final double MID_SERVO = 0.5 ; - public static final double CLAW_SPEED = 0.02 ; // sets rate to move servo - public static final double ARM_UP_POWER = 0.45 ; - public static final double ARM_DOWN_POWER = -0.45 ; - - @Override - public void runOpMode() { - double left; - double right; - double drive; - double turn; - double max; - - // Define and Initialize Motors - leftDrive = hardwareMap.get(DcMotor.class, "left_drive"); - rightDrive = hardwareMap.get(DcMotor.class, "right_drive"); - leftArm = hardwareMap.get(DcMotor.class, "left_arm"); - - // To drive forward, most robots need the motor on one side to be reversed, because the axles point in opposite directions. - // Pushing the left stick forward MUST make robot go forward. So adjust these two lines based on your first test drive. - // Note: The settings here assume direct drive on left and right wheels. Gear Reduction or 90 Deg drives may require direction flips - leftDrive.setDirection(DcMotor.Direction.REVERSE); - rightDrive.setDirection(DcMotor.Direction.FORWARD); - - // If there are encoders connected, switch to RUN_USING_ENCODER mode for greater accuracy - // leftDrive.setMode(DcMotor.RunMode.RUN_USING_ENCODER); - // rightDrive.setMode(DcMotor.RunMode.RUN_USING_ENCODER); - - // Define and initialize ALL installed servos. - leftClaw = hardwareMap.get(Servo.class, "left_hand"); - rightClaw = hardwareMap.get(Servo.class, "right_hand"); - leftClaw.setPosition(MID_SERVO); - rightClaw.setPosition(MID_SERVO); - - // Send telemetry message to signify robot waiting; - telemetry.addData(">", "Robot Ready. Press START."); // - telemetry.update(); - - // Wait for the game to start (driver presses START) - waitForStart(); - - // run until the end of the match (driver presses STOP) - while (opModeIsActive()) { - - // Run wheels in POV mode (note: The joystick goes negative when pushed forward, so negate it) - // In this mode the Left stick moves the robot fwd and back, the Right stick turns left and right. - // This way it's also easy to just drive straight, or just turn. - drive = -gamepad1.left_stick_y; - turn = gamepad1.right_stick_x; - - // Combine drive and turn for blended motion. - left = drive + turn; - right = drive - turn; - - // Normalize the values so neither exceed +/- 1.0 - max = Math.max(Math.abs(left), Math.abs(right)); - if (max > 1.0) - { - left /= max; - right /= max; - } - - // Output the safe vales to the motor drives. - leftDrive.setPower(left); - rightDrive.setPower(right); - - // Use gamepad left & right Bumpers to open and close the claw - if (gamepad1.right_bumper) - clawOffset += CLAW_SPEED; - else if (gamepad1.left_bumper) - clawOffset -= CLAW_SPEED; - - // Move both servos to new position. Assume servos are mirror image of each other. - clawOffset = Range.clip(clawOffset, -0.5, 0.5); - leftClaw.setPosition(MID_SERVO + clawOffset); - rightClaw.setPosition(MID_SERVO - clawOffset); - - // Use gamepad buttons to move arm up (Y) and down (A) - if (gamepad1.y) - leftArm.setPower(ARM_UP_POWER); - else if (gamepad1.a) - leftArm.setPower(ARM_DOWN_POWER); - else - leftArm.setPower(0.0); - - // Send telemetry message to signify robot running; - telemetry.addData("claw", "Offset = %.2f", clawOffset); - telemetry.addData("left", "%.2f", left); - telemetry.addData("right", "%.2f", right); - telemetry.update(); - - // Pace this loop so jaw action is reasonable speed. - sleep(50); - } - } -} diff --git a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/RobotTeleopTank_Iterative.java b/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/RobotTeleopTank_Iterative.java deleted file mode 100644 index a622f27..0000000 --- a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/RobotTeleopTank_Iterative.java +++ /dev/null @@ -1,160 +0,0 @@ -/* Copyright (c) 2017 FIRST. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted (subject to the limitations in the disclaimer below) provided that - * the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list - * of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this - * list of conditions and the following disclaimer in the documentation and/or - * other materials provided with the distribution. - * - * Neither the name of FIRST nor the names of its contributors may be used to endorse or - * promote products derived from this software without specific prior written permission. - * - * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS - * LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.firstinspires.ftc.robotcontroller.external.samples; - -import com.qualcomm.robotcore.eventloop.opmode.Disabled; -import com.qualcomm.robotcore.eventloop.opmode.OpMode; -import com.qualcomm.robotcore.eventloop.opmode.TeleOp; -import com.qualcomm.robotcore.hardware.DcMotor; -import com.qualcomm.robotcore.hardware.Servo; -import com.qualcomm.robotcore.util.Range; - -/* - * This OpMode executes a Tank Drive control TeleOp a direct drive robot - * The code is structured as an Iterative OpMode - * - * In this mode, the left and right joysticks control the left and right motors respectively. - * Pushing a joystick forward will make the attached motor drive forward. - * It raises and lowers the claw using the Gamepad Y and A buttons respectively. - * It also opens and closes the claws slowly using the left and right Bumper buttons. - * - * Use Android Studio to Copy this Class, and Paste it into your team's code folder with a new name. - * Remove or comment out the @Disabled line to add this OpMode to the Driver Station OpMode list - */ - -@TeleOp(name="Robot: Teleop Tank", group="Robot") -@Disabled -public class RobotTeleopTank_Iterative extends OpMode{ - - /* Declare OpMode members. */ - public DcMotor leftDrive = null; - public DcMotor rightDrive = null; - public DcMotor leftArm = null; - public Servo leftClaw = null; - public Servo rightClaw = null; - - double clawOffset = 0; - - public static final double MID_SERVO = 0.5 ; - public static final double CLAW_SPEED = 0.02 ; // sets rate to move servo - public static final double ARM_UP_POWER = 0.50 ; // Run arm motor up at 50% power - public static final double ARM_DOWN_POWER = -0.25 ; // Run arm motor down at -25% power - - /* - * Code to run ONCE when the driver hits INIT - */ - @Override - public void init() { - // Define and Initialize Motors - leftDrive = hardwareMap.get(DcMotor.class, "left_drive"); - rightDrive = hardwareMap.get(DcMotor.class, "right_drive"); - leftArm = hardwareMap.get(DcMotor.class, "left_arm"); - - // To drive forward, most robots need the motor on one side to be reversed, because the axles point in opposite directions. - // Pushing the left and right sticks forward MUST make robot go forward. So adjust these two lines based on your first test drive. - // Note: The settings here assume direct drive on left and right wheels. Gear Reduction or 90 Deg drives may require direction flips - leftDrive.setDirection(DcMotor.Direction.REVERSE); - rightDrive.setDirection(DcMotor.Direction.FORWARD); - - // If there are encoders connected, switch to RUN_USING_ENCODER mode for greater accuracy - // leftDrive.setMode(DcMotor.RunMode.RUN_USING_ENCODER); - // rightDrive.setMode(DcMotor.RunMode.RUN_USING_ENCODER); - - // Define and initialize ALL installed servos. - leftClaw = hardwareMap.get(Servo.class, "left_hand"); - rightClaw = hardwareMap.get(Servo.class, "right_hand"); - leftClaw.setPosition(MID_SERVO); - rightClaw.setPosition(MID_SERVO); - - // Send telemetry message to signify robot waiting; - telemetry.addData(">", "Robot Ready. Press START."); // - } - - /* - * Code to run REPEATEDLY after the driver hits INIT, but before they hit START - */ - @Override - public void init_loop() { - } - - /* - * Code to run ONCE when the driver hits START - */ - @Override - public void start() { - } - - /* - * Code to run REPEATEDLY after the driver hits START but before they hit STOP - */ - @Override - public void loop() { - double left; - double right; - - // Run wheels in tank mode (note: The joystick goes negative when pushed forward, so negate it) - left = -gamepad1.left_stick_y; - right = -gamepad1.right_stick_y; - - leftDrive.setPower(left); - rightDrive.setPower(right); - - // Use gamepad left & right Bumpers to open and close the claw - if (gamepad1.right_bumper) - clawOffset += CLAW_SPEED; - else if (gamepad1.left_bumper) - clawOffset -= CLAW_SPEED; - - // Move both servos to new position. Assume servos are mirror image of each other. - clawOffset = Range.clip(clawOffset, -0.5, 0.5); - leftClaw.setPosition(MID_SERVO + clawOffset); - rightClaw.setPosition(MID_SERVO - clawOffset); - - // Use gamepad buttons to move the arm up (Y) and down (A) - if (gamepad1.y) - leftArm.setPower(ARM_UP_POWER); - else if (gamepad1.a) - leftArm.setPower(ARM_DOWN_POWER); - else - leftArm.setPower(0.0); - - // Send telemetry message to signify robot running; - telemetry.addData("claw", "Offset = %.2f", clawOffset); - telemetry.addData("left", "%.2f", left); - telemetry.addData("right", "%.2f", right); - } - - /* - * Code to run ONCE after the driver hits STOP - */ - @Override - public void stop() { - } -} diff --git a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SampleRevBlinkinLedDriver.java b/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SampleRevBlinkinLedDriver.java deleted file mode 100644 index bcf5b80..0000000 --- a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SampleRevBlinkinLedDriver.java +++ /dev/null @@ -1,163 +0,0 @@ -/* - * Copyright (c) 2018 Craig MacFarlane - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted - * (subject to the limitations in the disclaimer below) provided that the following conditions are - * met: - * - * Redistributions of source code must retain the above copyright notice, this list of conditions - * and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this list of conditions - * and the following disclaimer in the documentation and/or other materials provided with the - * distribution. - * - * Neither the name of Craig MacFarlane nor the names of its contributors may be used to - * endorse or promote products derived from this software without specific prior written permission. - * - * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS LICENSE. THIS - * SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS - * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, - * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF - * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package org.firstinspires.ftc.robotcontroller.external.samples; - -import com.qualcomm.hardware.rev.RevBlinkinLedDriver; -import com.qualcomm.robotcore.eventloop.opmode.Disabled; -import com.qualcomm.robotcore.eventloop.opmode.OpMode; -import com.qualcomm.robotcore.eventloop.opmode.TeleOp; -import org.firstinspires.ftc.robotcore.external.Telemetry; -import org.firstinspires.ftc.robotcore.internal.system.Deadline; - -import java.util.concurrent.TimeUnit; - -/* - * This OpMode demonstrates use of the REV Robotics Blinkin LED Driver. - * AUTO mode cycles through all of the patterns. - * MANUAL mode allows the user to manually change patterns using the - * left and right bumpers of a gamepad. - * - * Configure the driver on a servo port, and name it "blinkin". - * - * Displays the first pattern upon init. - */ -@TeleOp(name="BlinkinExample") -@Disabled -public class SampleRevBlinkinLedDriver extends OpMode { - - /* - * Change the pattern every 10 seconds in AUTO mode. - */ - private final static int LED_PERIOD = 10; - - /* - * Rate limit gamepad button presses to every 500ms. - */ - private final static int GAMEPAD_LOCKOUT = 500; - - RevBlinkinLedDriver blinkinLedDriver; - RevBlinkinLedDriver.BlinkinPattern pattern; - - Telemetry.Item patternName; - Telemetry.Item display; - DisplayKind displayKind; - Deadline ledCycleDeadline; - Deadline gamepadRateLimit; - - protected enum DisplayKind { - MANUAL, - AUTO - } - - @Override - public void init() - { - displayKind = DisplayKind.AUTO; - - blinkinLedDriver = hardwareMap.get(RevBlinkinLedDriver.class, "blinkin"); - pattern = RevBlinkinLedDriver.BlinkinPattern.RAINBOW_RAINBOW_PALETTE; - blinkinLedDriver.setPattern(pattern); - - display = telemetry.addData("Display Kind: ", displayKind.toString()); - patternName = telemetry.addData("Pattern: ", pattern.toString()); - - ledCycleDeadline = new Deadline(LED_PERIOD, TimeUnit.SECONDS); - gamepadRateLimit = new Deadline(GAMEPAD_LOCKOUT, TimeUnit.MILLISECONDS); - } - - @Override - public void loop() - { - handleGamepad(); - - if (displayKind == DisplayKind.AUTO) { - doAutoDisplay(); - } else { - /* - * MANUAL mode: Nothing to do, setting the pattern as a result of a gamepad event. - */ - } - } - - /* - * handleGamepad - * - * Responds to a gamepad button press. Demonstrates rate limiting for - * button presses. If loop() is called every 10ms and and you don't rate - * limit, then any given button press may register as multiple button presses, - * which in this application is problematic. - * - * A: Manual mode, Right bumper displays the next pattern, left bumper displays the previous pattern. - * B: Auto mode, pattern cycles, changing every LED_PERIOD seconds. - */ - protected void handleGamepad() - { - if (!gamepadRateLimit.hasExpired()) { - return; - } - - if (gamepad1.a) { - setDisplayKind(DisplayKind.MANUAL); - gamepadRateLimit.reset(); - } else if (gamepad1.b) { - setDisplayKind(DisplayKind.AUTO); - gamepadRateLimit.reset(); - } else if ((displayKind == DisplayKind.MANUAL) && (gamepad1.left_bumper)) { - pattern = pattern.previous(); - displayPattern(); - gamepadRateLimit.reset(); - } else if ((displayKind == DisplayKind.MANUAL) && (gamepad1.right_bumper)) { - pattern = pattern.next(); - displayPattern(); - gamepadRateLimit.reset(); - } - } - - protected void setDisplayKind(DisplayKind displayKind) - { - this.displayKind = displayKind; - display.setValue(displayKind.toString()); - } - - protected void doAutoDisplay() - { - if (ledCycleDeadline.hasExpired()) { - pattern = pattern.next(); - displayPattern(); - ledCycleDeadline.reset(); - } - } - - protected void displayPattern() - { - blinkinLedDriver.setPattern(pattern); - patternName.setValue(pattern.toString()); - } -} diff --git a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorBNO055IMU.java b/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorBNO055IMU.java deleted file mode 100644 index 405da1e..0000000 --- a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorBNO055IMU.java +++ /dev/null @@ -1,186 +0,0 @@ -/* Copyright (c) 2017 FIRST. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted (subject to the limitations in the disclaimer below) provided that - * the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list - * of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this - * list of conditions and the following disclaimer in the documentation and/or - * other materials provided with the distribution. - * - * Neither the name of FIRST nor the names of its contributors may be used to endorse or - * promote products derived from this software without specific prior written permission. - * - * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS - * LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.firstinspires.ftc.robotcontroller.external.samples; - -import com.qualcomm.hardware.bosch.BNO055IMU; -import com.qualcomm.hardware.bosch.JustLoggingAccelerationIntegrator; -import com.qualcomm.robotcore.eventloop.opmode.Disabled; -import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode; -import com.qualcomm.robotcore.eventloop.opmode.TeleOp; -import org.firstinspires.ftc.robotcore.external.Func; -import org.firstinspires.ftc.robotcore.external.navigation.Acceleration; -import org.firstinspires.ftc.robotcore.external.navigation.AngleUnit; -import org.firstinspires.ftc.robotcore.external.navigation.AxesOrder; -import org.firstinspires.ftc.robotcore.external.navigation.AxesReference; -import org.firstinspires.ftc.robotcore.external.navigation.Orientation; -import org.firstinspires.ftc.robotcore.external.navigation.Position; -import org.firstinspires.ftc.robotcore.external.navigation.Velocity; - -import java.util.Locale; - -/* - * This OpMode gives a short demo on how to use the BNO055 Inertial Motion Unit (IMU) from AdaFruit. - * - * Note: this is a Legacy example that will not work with newer Control/Expansion Hubs that use a different IMU - * Please use the new SensorIMUOrthogonal or SensorIMUNonOrthogonal samples for a more universal IMU interface. - * - * Use Android Studio to Copy this Class, and Paste it into your team's code folder with a new name. - * Remove or comment out the @Disabled line to add this OpMode to the Driver Station OpMode list - * - * @see Adafruit IMU - */ -@TeleOp(name = "Sensor: BNO055 IMU", group = "Sensor") -@Disabled // Comment this out to add to the OpMode list -public class SensorBNO055IMU extends LinearOpMode - { - //---------------------------------------------------------------------------------------------- - // State - //---------------------------------------------------------------------------------------------- - - // The IMU sensor object - BNO055IMU imu; - - // State used for updating telemetry - Orientation angles; - Acceleration gravity; - - //---------------------------------------------------------------------------------------------- - // Main logic - //---------------------------------------------------------------------------------------------- - - @Override public void runOpMode() { - - // Set up the parameters with which we will use our IMU. Note that integration - // algorithm here just reports accelerations to the logcat log; it doesn't actually - // provide positional information. - BNO055IMU.Parameters parameters = new BNO055IMU.Parameters(); - parameters.angleUnit = BNO055IMU.AngleUnit.DEGREES; - parameters.accelUnit = BNO055IMU.AccelUnit.METERS_PERSEC_PERSEC; - parameters.calibrationDataFile = "BNO055IMUCalibration.json"; // see the calibration sample OpMode - parameters.loggingEnabled = true; - parameters.loggingTag = "IMU"; - parameters.accelerationIntegrationAlgorithm = new JustLoggingAccelerationIntegrator(); - - // Retrieve and initialize the IMU. We expect the IMU to be attached to an I2C port - // on a Core Device Interface Module, configured to be a sensor of type "AdaFruit IMU", - // and named "imu". - imu = hardwareMap.get(BNO055IMU.class, "imu"); - imu.initialize(parameters); - - // Set up our telemetry dashboard - composeTelemetry(); - - // Wait until we're told to go - waitForStart(); - - // Start the logging of measured acceleration - imu.startAccelerationIntegration(new Position(), new Velocity(), 1000); - - // Loop and update the dashboard - while (opModeIsActive()) { - telemetry.update(); - } - } - - //---------------------------------------------------------------------------------------------- - // Telemetry Configuration - //---------------------------------------------------------------------------------------------- - - void composeTelemetry() { - - // At the beginning of each telemetry update, grab a bunch of data - // from the IMU that we will then display in separate lines. - telemetry.addAction(new Runnable() { @Override public void run() - { - // Acquiring the angles is relatively expensive; we don't want - // to do that in each of the three items that need that info, as that's - // three times the necessary expense. - angles = imu.getAngularOrientation(AxesReference.INTRINSIC, AxesOrder.ZYX, AngleUnit.DEGREES); - gravity = imu.getGravity(); - } - }); - - telemetry.addLine() - .addData("status", new Func() { - @Override public String value() { - return imu.getSystemStatus().toShortString(); - } - }) - .addData("calib", new Func() { - @Override public String value() { - return imu.getCalibrationStatus().toString(); - } - }); - - telemetry.addLine() - .addData("heading", new Func() { - @Override public String value() { - return formatAngle(angles.angleUnit, angles.firstAngle); - } - }) - .addData("roll", new Func() { - @Override public String value() { - return formatAngle(angles.angleUnit, angles.secondAngle); - } - }) - .addData("pitch", new Func() { - @Override public String value() { - return formatAngle(angles.angleUnit, angles.thirdAngle); - } - }); - - telemetry.addLine() - .addData("grvty", new Func() { - @Override public String value() { - return gravity.toString(); - } - }) - .addData("mag", new Func() { - @Override public String value() { - return String.format(Locale.getDefault(), "%.3f", - Math.sqrt(gravity.xAccel*gravity.xAccel - + gravity.yAccel*gravity.yAccel - + gravity.zAccel*gravity.zAccel)); - } - }); - } - - //---------------------------------------------------------------------------------------------- - // Formatting - //---------------------------------------------------------------------------------------------- - - String formatAngle(AngleUnit angleUnit, double angle) { - return formatDegrees(AngleUnit.DEGREES.fromUnit(angleUnit, angle)); - } - - String formatDegrees(double degrees){ - return String.format(Locale.getDefault(), "%.1f", AngleUnit.DEGREES.normalize(degrees)); - } -} diff --git a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorBNO055IMUCalibration.java b/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorBNO055IMUCalibration.java deleted file mode 100644 index 93f1789..0000000 --- a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorBNO055IMUCalibration.java +++ /dev/null @@ -1,230 +0,0 @@ -/* Copyright (c) 2017 FIRST. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted (subject to the limitations in the disclaimer below) provided that - * the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list - * of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this - * list of conditions and the following disclaimer in the documentation and/or - * other materials provided with the distribution. - * - * Neither the name of FIRST nor the names of its contributors may be used to endorse or - * promote products derived from this software without specific prior written permission. - * - * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS - * LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.firstinspires.ftc.robotcontroller.external.samples; - -import com.qualcomm.hardware.bosch.BNO055IMU; -import com.qualcomm.robotcore.eventloop.opmode.Disabled; -import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode; -import com.qualcomm.robotcore.eventloop.opmode.TeleOp; -import com.qualcomm.robotcore.util.ReadWriteFile; -import org.firstinspires.ftc.robotcore.external.Func; -import org.firstinspires.ftc.robotcore.external.navigation.AngleUnit; -import org.firstinspires.ftc.robotcore.external.navigation.AxesOrder; -import org.firstinspires.ftc.robotcore.external.navigation.AxesReference; -import org.firstinspires.ftc.robotcore.external.navigation.Orientation; -import org.firstinspires.ftc.robotcore.internal.system.AppUtil; - -import java.io.File; -import java.util.Locale; - -/* - * This OpMode calibrates a BNO055 IMU per - * "Section 3.11 Calibration" of the BNO055 specification. - * - * Note: this is a Legacy example that will not work with newer Control/Expansion Hubs that use a different IMU - * Please use the new SensorIMUOrthogonal or SensorIMUNonOrthogonal samples for a more universal IMU interface. - * - * Manual calibration of the IMU is definitely NOT necessary: except for the magnetometer (which is not used by the - * default "IMU" SensorMode), the BNO055 is internally self-calibrating and thus can be very successfully used without - * manual intervention. That said, performing a one-time calibration, saving the results persistently, then loading them - * again at each run can help reduce the time that automatic calibration requires. - * - * This summary of the calibration process from Intel is informative: - * http://iotdk.intel.com/docs/master/upm/classupm_1_1_b_n_o055.html - * - * "This device requires calibration in order to operate accurately. [...] Calibration data is - * lost on a power cycle. See one of the examples for a description of how to calibrate the device, - * but in essence: - * - * There is a calibration status register available [...] that returns the calibration status - * of the accelerometer (ACC), magnetometer (MAG), gyroscope (GYR), and overall system (SYS). - * Each of these values range from 0 (uncalibrated) to 3 (fully calibrated). Calibration [ideally] - * involves certain motions to get all 4 values at 3. The motions are as follows (though see the - * datasheet for more information): - * - * 1. GYR: Simply let the sensor sit flat for a few seconds. - * 2. ACC: Move the sensor in various positions. Start flat, then rotate slowly by 45 - * degrees, hold for a few seconds, then continue rotating another 45 degrees and - * hold, etc. 6 or more movements of this type may be required. You can move through - * any axis you desire, but make sure that the device is lying at least once - * perpendicular to the x, y, and z axis. - * 3. MAG: Move slowly in a figure 8 pattern in the air, until the calibration values reaches 3. - * 4. SYS: This will usually reach 3 when the other items have also reached 3. If not, continue - * slowly moving the device though various axes until it does." - * - * To calibrate the IMU, run this sample OpMode with a gamepad attached to the driver station. - * Once the IMU has reached sufficient calibration as reported on telemetry, press the 'A' - * button on the gamepad to write the calibration to a file. That file can then be indicated - * later when running an OpMode which uses the IMU. - * - * Note: if your intended uses of the IMU do not include use of all its sensors (for example, - * you might not use the magnetometer), then it makes little sense for you to wait for full - * calibration of the sensors you are not using before saving the calibration data. Indeed, - * it appears that in a SensorMode that doesn't use the magnetometer (for example), the - * magnetometer cannot actually be calibrated. - * - * References: - * The AdafruitBNO055IMU Javadoc - * The BNO055IMU.Parameters.calibrationDataFile Javadoc - * The BNO055 product page: https://www.bosch-sensortec.com/bst/products/all_products/bno055 - * The BNO055 datasheet: https://www.bosch-sensortec.com/media/boschsensortec/downloads/datasheets/bst-bno055-ds000.pdf - */ -@TeleOp(name = "Sensor: BNO055 IMU Calibration", group = "Sensor") -@Disabled // Uncomment this to add to the OpMode list -public class SensorBNO055IMUCalibration extends LinearOpMode - { - //---------------------------------------------------------------------------------------------- - // State - //---------------------------------------------------------------------------------------------- - - // Our sensors, motors, and other devices go here, along with other long term state - BNO055IMU imu; - - // State used for updating telemetry - Orientation angles; - - //---------------------------------------------------------------------------------------------- - // Main logic - //---------------------------------------------------------------------------------------------- - - @Override public void runOpMode() { - - telemetry.log().setCapacity(12); - telemetry.log().add(""); - telemetry.log().add("Please refer to the calibration instructions"); - telemetry.log().add("contained in the Adafruit IMU calibration"); - telemetry.log().add("sample OpMode."); - telemetry.log().add(""); - telemetry.log().add("When sufficient calibration has been reached,"); - telemetry.log().add("press the 'A' button to write the current"); - telemetry.log().add("calibration data to a file."); - telemetry.log().add(""); - - // We are expecting the IMU to be attached to an I2C port on a Core Device Interface Module and named "imu". - BNO055IMU.Parameters parameters = new BNO055IMU.Parameters(); - parameters.loggingEnabled = true; - parameters.loggingTag = "IMU"; - imu = hardwareMap.get(BNO055IMU.class, "imu"); - imu.initialize(parameters); - - composeTelemetry(); - telemetry.log().add("Waiting for start..."); - - // Wait until we're told to go - while (!isStarted()) { - telemetry.update(); - idle(); - } - - telemetry.log().add("...started..."); - - while (opModeIsActive()) { - - if (gamepad1.a) { - - // Get the calibration data - BNO055IMU.CalibrationData calibrationData = imu.readCalibrationData(); - - // Save the calibration data to a file. You can choose whatever file - // name you wish here, but you'll want to indicate the same file name - // when you initialize the IMU in an OpMode in which it is used. If you - // have more than one IMU on your robot, you'll of course want to use - // different configuration file names for each. - String filename = "AdafruitIMUCalibration.json"; - File file = AppUtil.getInstance().getSettingsFile(filename); - ReadWriteFile.writeFile(file, calibrationData.serialize()); - telemetry.log().add("saved to '%s'", filename); - - // Wait for the button to be released - while (gamepad1.a) { - telemetry.update(); - idle(); - } - } - - telemetry.update(); - } - } - - void composeTelemetry() { - - // At the beginning of each telemetry update, grab a bunch of data - // from the IMU that we will then display in separate lines. - telemetry.addAction(new Runnable() { @Override public void run() - { - // Acquiring the angles is relatively expensive; we don't want - // to do that in each of the three items that need that info, as that's - // three times the necessary expense. - angles = imu.getAngularOrientation(AxesReference.INTRINSIC, AxesOrder.ZYX, AngleUnit.DEGREES); - } - }); - - telemetry.addLine() - .addData("status", new Func() { - @Override public String value() { - return imu.getSystemStatus().toShortString(); - } - }) - .addData("calib", new Func() { - @Override public String value() { - return imu.getCalibrationStatus().toString(); - } - }); - - telemetry.addLine() - .addData("heading", new Func() { - @Override public String value() { - return formatAngle(angles.angleUnit, angles.firstAngle); - } - }) - .addData("roll", new Func() { - @Override public String value() { - return formatAngle(angles.angleUnit, angles.secondAngle); - } - }) - .addData("pitch", new Func() { - @Override public String value() { - return formatAngle(angles.angleUnit, angles.thirdAngle); - } - }); - } - - //---------------------------------------------------------------------------------------------- - // Formatting - //---------------------------------------------------------------------------------------------- - - String formatAngle(AngleUnit angleUnit, double angle) { - return formatDegrees(AngleUnit.DEGREES.fromUnit(angleUnit, angle)); - } - - String formatDegrees(double degrees){ - return String.format(Locale.getDefault(), "%.1f", AngleUnit.DEGREES.normalize(degrees)); - } -} diff --git a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorColor.java b/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorColor.java deleted file mode 100644 index 7546c9d..0000000 --- a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorColor.java +++ /dev/null @@ -1,221 +0,0 @@ -/* Copyright (c) 2017-2020 FIRST. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted (subject to the limitations in the disclaimer below) provided that - * the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list - * of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this - * list of conditions and the following disclaimer in the documentation and/or - * other materials provided with the distribution. - * - * Neither the name of FIRST nor the names of its contributors may be used to endorse or - * promote products derived from this software without specific prior written permission. - * - * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS - * LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.firstinspires.ftc.robotcontroller.external.samples; - -import android.app.Activity; -import android.graphics.Color; -import android.view.View; -import com.qualcomm.robotcore.eventloop.opmode.Disabled; -import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode; -import com.qualcomm.robotcore.eventloop.opmode.TeleOp; -import com.qualcomm.robotcore.hardware.DistanceSensor; -import com.qualcomm.robotcore.hardware.NormalizedColorSensor; -import com.qualcomm.robotcore.hardware.NormalizedRGBA; -import com.qualcomm.robotcore.hardware.SwitchableLight; -import org.firstinspires.ftc.robotcore.external.navigation.DistanceUnit; - -/* - * This OpMode shows how to use a color sensor in a generic - * way, regardless of which particular make or model of color sensor is used. The OpMode - * assumes that the color sensor is configured with a name of "sensor_color". - * - * There will be some variation in the values measured depending on the specific sensor you are using. - * - * You can increase the gain (a multiplier to make the sensor report higher values) by holding down - * the A button on the gamepad, and decrease the gain by holding down the B button on the gamepad. - * - * If the color sensor has a light which is controllable from software, you can use the X button on - * the gamepad to toggle the light on and off. The REV sensors don't support this, but instead have - * a physical switch on them to turn the light on and off, beginning with REV Color Sensor V2. - * - * If the color sensor also supports short-range distance measurements (usually via an infrared - * proximity sensor), the reported distance will be written to telemetry. As of September 2020, - * the only color sensors that support this are the ones from REV Robotics. These infrared proximity - * sensor measurements are only useful at very small distances, and are sensitive to ambient light - * and surface reflectivity. You should use a different sensor if you need precise distance measurements. - * - * Use Android Studio to Copy this Class, and Paste it into your team's code folder with a new name. - * Remove or comment out the @Disabled line to add this OpMode to the Driver Station OpMode list - */ -@TeleOp(name = "Sensor: Color", group = "Sensor") -@Disabled -public class SensorColor extends LinearOpMode { - - /** The colorSensor field will contain a reference to our color sensor hardware object */ - NormalizedColorSensor colorSensor; - - /** The relativeLayout field is used to aid in providing interesting visual feedback - * in this sample application; you probably *don't* need this when you use a color sensor on your - * robot. Note that you won't see anything change on the Driver Station, only on the Robot Controller. */ - View relativeLayout; - - /* - * The runOpMode() method is the root of this OpMode, as it is in all LinearOpModes. - * Our implementation here, though is a bit unusual: we've decided to put all the actual work - * in the runSample() method rather than directly in runOpMode() itself. The reason we do that is - * that in this sample we're changing the background color of the robot controller screen as the - * OpMode runs, and we want to be able to *guarantee* that we restore it to something reasonable - * and palatable when the OpMode ends. The simplest way to do that is to use a try...finally - * block around the main, core logic, and an easy way to make that all clear was to separate - * the former from the latter in separate methods. - */ - @Override public void runOpMode() { - - // Get a reference to the RelativeLayout so we can later change the background - // color of the Robot Controller app to match the hue detected by the RGB sensor. - int relativeLayoutId = hardwareMap.appContext.getResources().getIdentifier("RelativeLayout", "id", hardwareMap.appContext.getPackageName()); - relativeLayout = ((Activity) hardwareMap.appContext).findViewById(relativeLayoutId); - - try { - runSample(); // actually execute the sample - } finally { - // On the way out, *guarantee* that the background is reasonable. It doesn't actually start off - // as pure white, but it's too much work to dig out what actually was used, and this is good - // enough to at least make the screen reasonable again. - // Set the panel back to the default color - relativeLayout.post(new Runnable() { - public void run() { - relativeLayout.setBackgroundColor(Color.WHITE); - } - }); - } - } - - protected void runSample() { - // You can give the sensor a gain value, will be multiplied by the sensor's raw value before the - // normalized color values are calculated. Color sensors (especially the REV Color Sensor V3) - // can give very low values (depending on the lighting conditions), which only use a small part - // of the 0-1 range that is available for the red, green, and blue values. In brighter conditions, - // you should use a smaller gain than in dark conditions. If your gain is too high, all of the - // colors will report at or near 1, and you won't be able to determine what color you are - // actually looking at. For this reason, it's better to err on the side of a lower gain - // (but always greater than or equal to 1). - float gain = 2; - - // Once per loop, we will update this hsvValues array. The first element (0) will contain the - // hue, the second element (1) will contain the saturation, and the third element (2) will - // contain the value. See http://web.archive.org/web/20190311170843/https://infohost.nmt.edu/tcc/help/pubs/colortheory/web/hsv.html - // for an explanation of HSV color. - final float[] hsvValues = new float[3]; - - // xButtonPreviouslyPressed and xButtonCurrentlyPressed keep track of the previous and current - // state of the X button on the gamepad - boolean xButtonPreviouslyPressed = false; - boolean xButtonCurrentlyPressed = false; - - // Get a reference to our sensor object. It's recommended to use NormalizedColorSensor over - // ColorSensor, because NormalizedColorSensor consistently gives values between 0 and 1, while - // the values you get from ColorSensor are dependent on the specific sensor you're using. - colorSensor = hardwareMap.get(NormalizedColorSensor.class, "sensor_color"); - - // If possible, turn the light on in the beginning (it might already be on anyway, - // we just make sure it is if we can). - if (colorSensor instanceof SwitchableLight) { - ((SwitchableLight)colorSensor).enableLight(true); - } - - // Wait for the start button to be pressed. - waitForStart(); - - // Loop until we are asked to stop - while (opModeIsActive()) { - // Explain basic gain information via telemetry - telemetry.addLine("Hold the A button on gamepad 1 to increase gain, or B to decrease it.\n"); - telemetry.addLine("Higher gain values mean that the sensor will report larger numbers for Red, Green, and Blue, and Value\n"); - - // Update the gain value if either of the A or B gamepad buttons is being held - if (gamepad1.a) { - // Only increase the gain by a small amount, since this loop will occur multiple times per second. - gain += 0.005; - } else if (gamepad1.b && gain > 1) { // A gain of less than 1 will make the values smaller, which is not helpful. - gain -= 0.005; - } - - // Show the gain value via telemetry - telemetry.addData("Gain", gain); - - // Tell the sensor our desired gain value (normally you would do this during initialization, - // not during the loop) - colorSensor.setGain(gain); - - // Check the status of the X button on the gamepad - xButtonCurrentlyPressed = gamepad1.x; - - // If the button state is different than what it was, then act - if (xButtonCurrentlyPressed != xButtonPreviouslyPressed) { - // If the button is (now) down, then toggle the light - if (xButtonCurrentlyPressed) { - if (colorSensor instanceof SwitchableLight) { - SwitchableLight light = (SwitchableLight)colorSensor; - light.enableLight(!light.isLightOn()); - } - } - } - xButtonPreviouslyPressed = xButtonCurrentlyPressed; - - // Get the normalized colors from the sensor - NormalizedRGBA colors = colorSensor.getNormalizedColors(); - - /* Use telemetry to display feedback on the driver station. We show the red, green, and blue - * normalized values from the sensor (in the range of 0 to 1), as well as the equivalent - * HSV (hue, saturation and value) values. See http://web.archive.org/web/20190311170843/https://infohost.nmt.edu/tcc/help/pubs/colortheory/web/hsv.html - * for an explanation of HSV color. */ - - // Update the hsvValues array by passing it to Color.colorToHSV() - Color.colorToHSV(colors.toColor(), hsvValues); - - telemetry.addLine() - .addData("Red", "%.3f", colors.red) - .addData("Green", "%.3f", colors.green) - .addData("Blue", "%.3f", colors.blue); - telemetry.addLine() - .addData("Hue", "%.3f", hsvValues[0]) - .addData("Saturation", "%.3f", hsvValues[1]) - .addData("Value", "%.3f", hsvValues[2]); - telemetry.addData("Alpha", "%.3f", colors.alpha); - - /* If this color sensor also has a distance sensor, display the measured distance. - * Note that the reported distance is only useful at very close range, and is impacted by - * ambient light and surface reflectivity. */ - if (colorSensor instanceof DistanceSensor) { - telemetry.addData("Distance (cm)", "%.3f", ((DistanceSensor) colorSensor).getDistance(DistanceUnit.CM)); - } - - telemetry.update(); - - // Change the Robot Controller's background color to match the color detected by the color sensor. - relativeLayout.post(new Runnable() { - public void run() { - relativeLayout.setBackgroundColor(Color.HSVToColor(hsvValues)); - } - }); - } - } -} diff --git a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorDigitalTouch.java b/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorDigitalTouch.java deleted file mode 100644 index 44c3ca9..0000000 --- a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorDigitalTouch.java +++ /dev/null @@ -1,78 +0,0 @@ -/* Copyright (c) 2024 FIRST. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted (subject to the limitations in the disclaimer below) provided that - * the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list - * of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this - * list of conditions and the following disclaimer in the documentation and/or - * other materials provided with the distribution. - * - * Neither the name of FIRST nor the names of its contributors may be used to endorse or - * promote products derived from this software without specific prior written permission. - * - * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS - * LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.firstinspires.ftc.robotcontroller.external.samples; - -import com.qualcomm.robotcore.eventloop.opmode.Disabled; -import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode; -import com.qualcomm.robotcore.eventloop.opmode.TeleOp; -import com.qualcomm.robotcore.hardware.DigitalChannel; - -/* - * This OpMode demonstrates how to use a digital channel. - * - * The OpMode assumes that the digital channel is configured with a name of "digitalTouch". - * - * Use Android Studio to Copy this Class, and Paste it into your team's code folder with a new name. - * Remove or comment out the @Disabled line to add this OpMode to the Driver Station OpMode list. - */ -@TeleOp(name = "Sensor: digital channel", group = "Sensor") -@Disabled -public class SensorDigitalTouch extends LinearOpMode { - DigitalChannel digitalTouch; // Digital channel Object - - @Override - public void runOpMode() { - - // get a reference to our touchSensor object. - digitalTouch = hardwareMap.get(DigitalChannel.class, "digitalTouch"); - - digitalTouch.setMode(DigitalChannel.Mode.INPUT); - telemetry.addData("DigitalTouchSensorExample", "Press start to continue..."); - telemetry.update(); - - // wait for the start button to be pressed. - waitForStart(); - - // while the OpMode is active, loop and read the digital channel. - // Note we use opModeIsActive() as our loop condition because it is an interruptible method. - while (opModeIsActive()) { - - // button is pressed if value returned is LOW or false. - // send the info back to driver station using telemetry function. - if (digitalTouch.getState() == false) { - telemetry.addData("Button", "PRESSED"); - } else { - telemetry.addData("Button", "NOT PRESSED"); - } - - telemetry.update(); - } - } -} diff --git a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorHuskyLens.java b/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorHuskyLens.java deleted file mode 100644 index af7ca55..0000000 --- a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorHuskyLens.java +++ /dev/null @@ -1,160 +0,0 @@ -/* -Copyright (c) 2023 FIRST - -All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted (subject to the limitations in the disclaimer below) provided that -the following conditions are met: - -Redistributions of source code must retain the above copyright notice, this list -of conditions and the following disclaimer. - -Redistributions in binary form must reproduce the above copyright notice, this -list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. - -Neither the name of FIRST nor the names of its contributors may be used to -endorse or promote products derived from this software without specific prior -written permission. - -NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS -LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR -TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF -THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ -package org.firstinspires.ftc.robotcontroller.external.samples; - -import com.qualcomm.hardware.dfrobot.HuskyLens; -import com.qualcomm.robotcore.eventloop.opmode.Disabled; -import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode; -import com.qualcomm.robotcore.eventloop.opmode.TeleOp; - -import org.firstinspires.ftc.robotcore.internal.system.Deadline; - -import java.util.concurrent.TimeUnit; - -/* - * This OpMode illustrates how to use the DFRobot HuskyLens. - * - * The HuskyLens is a Vision Sensor with a built-in object detection model. It can - * detect a number of predefined objects and AprilTags in the 36h11 family, can - * recognize colors, and can be trained to detect custom objects. See this website for - * documentation: https://wiki.dfrobot.com/HUSKYLENS_V1.0_SKU_SEN0305_SEN0336 - * - * For detailed instructions on how a HuskyLens is used in FTC, please see this tutorial: - * https://ftc-docs.firstinspires.org/en/latest/devices/huskylens/huskylens.html - * - * This sample illustrates how to detect AprilTags, but can be used to detect other types - * of objects by changing the algorithm. It assumes that the HuskyLens is configured with - * a name of "huskylens". - * - * Use Android Studio to Copy this Class, and Paste it into your team's code folder with a new name. - * Remove or comment out the @Disabled line to add this OpMode to the Driver Station OpMode list - */ -@TeleOp(name = "Sensor: HuskyLens", group = "Sensor") -@Disabled -public class SensorHuskyLens extends LinearOpMode { - - private final int READ_PERIOD = 1; - - private HuskyLens huskyLens; - - @Override - public void runOpMode() - { - huskyLens = hardwareMap.get(HuskyLens.class, "huskylens"); - - /* - * This sample rate limits the reads solely to allow a user time to observe - * what is happening on the Driver Station telemetry. Typical applications - * would not likely rate limit. - */ - Deadline rateLimit = new Deadline(READ_PERIOD, TimeUnit.SECONDS); - - /* - * Immediately expire so that the first time through we'll do the read. - */ - rateLimit.expire(); - - /* - * Basic check to see if the device is alive and communicating. This is not - * technically necessary here as the HuskyLens class does this in its - * doInitialization() method which is called when the device is pulled out of - * the hardware map. However, sometimes it's unclear why a device reports as - * failing on initialization. In the case of this device, it's because the - * call to knock() failed. - */ - if (!huskyLens.knock()) { - telemetry.addData(">>", "Problem communicating with " + huskyLens.getDeviceName()); - } else { - telemetry.addData(">>", "Press start to continue"); - } - - /* - * The device uses the concept of an algorithm to determine what types of - * objects it will look for and/or what mode it is in. The algorithm may be - * selected using the scroll wheel on the device, or via software as shown in - * the call to selectAlgorithm(). - * - * The SDK itself does not assume that the user wants a particular algorithm on - * startup, and hence does not set an algorithm. - * - * Users, should, in general, explicitly choose the algorithm they want to use - * within the OpMode by calling selectAlgorithm() and passing it one of the values - * found in the enumeration HuskyLens.Algorithm. - * - * Other algorithm choices for FTC might be: OBJECT_RECOGNITION, COLOR_RECOGNITION or OBJECT_CLASSIFICATION. - */ - huskyLens.selectAlgorithm(HuskyLens.Algorithm.TAG_RECOGNITION); - - telemetry.update(); - waitForStart(); - - /* - * Looking for AprilTags per the call to selectAlgorithm() above. A handy grid - * for testing may be found at https://wiki.dfrobot.com/HUSKYLENS_V1.0_SKU_SEN0305_SEN0336#target_20. - * - * Note again that the device only recognizes the 36h11 family of tags out of the box. - */ - while(opModeIsActive()) { - if (!rateLimit.hasExpired()) { - continue; - } - rateLimit.reset(); - - /* - * All algorithms, except for LINE_TRACKING, return a list of Blocks where a - * Block represents the outline of a recognized object along with its ID number. - * ID numbers allow you to identify what the device saw. See the HuskyLens documentation - * referenced in the header comment above for more information on IDs and how to - * assign them to objects. - * - * Returns an empty array if no objects are seen. - */ - HuskyLens.Block[] blocks = huskyLens.blocks(); - telemetry.addData("Block count", blocks.length); - for (int i = 0; i < blocks.length; i++) { - telemetry.addData("Block", blocks[i].toString()); - /* - * Here inside the FOR loop, you could save or evaluate specific info for the currently recognized Bounding Box: - * - blocks[i].width and blocks[i].height (size of box, in pixels) - * - blocks[i].left and blocks[i].top (edges of box) - * - blocks[i].x and blocks[i].y (center location) - * - blocks[i].id (Color ID) - * - * These values have Java type int (integer). - */ - } - - telemetry.update(); - } - } -} \ No newline at end of file diff --git a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorIMUNonOrthogonal.java b/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorIMUNonOrthogonal.java deleted file mode 100644 index 70bc8d4..0000000 --- a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorIMUNonOrthogonal.java +++ /dev/null @@ -1,184 +0,0 @@ -/* Copyright (c) 2022 FIRST. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted (subject to the limitations in the disclaimer below) provided that - * the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list - * of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this - * list of conditions and the following disclaimer in the documentation and/or - * other materials provided with the distribution. - * - * Neither the name of FIRST nor the names of its contributors may be used to endorse or - * promote products derived from this software without specific prior written permission. - * - * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS - * LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.firstinspires.ftc.robotcontroller.external.samples; - -import com.qualcomm.hardware.rev.RevHubOrientationOnRobot; -import com.qualcomm.robotcore.eventloop.opmode.Disabled; -import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode; -import com.qualcomm.robotcore.eventloop.opmode.TeleOp; -import com.qualcomm.robotcore.hardware.IMU; -import org.firstinspires.ftc.robotcore.external.navigation.AngleUnit; -import org.firstinspires.ftc.robotcore.external.navigation.AngularVelocity; -import org.firstinspires.ftc.robotcore.external.navigation.Orientation; -import org.firstinspires.ftc.robotcore.external.navigation.YawPitchRollAngles; - -import static com.qualcomm.hardware.rev.RevHubOrientationOnRobot.xyzOrientation; - -/* - * This OpMode shows how to use the new universal IMU interface. This - * interface may be used with the BNO055 IMU or the BHI260 IMU. It assumes that an IMU is configured - * on the robot with the name "imu". - * - * The sample will display the current Yaw, Pitch and Roll of the robot.
- * With the correct orientation parameters selected, pitch/roll/yaw should act as follows: - * Pitch value should INCREASE as the robot is tipped UP at the front. (Rotation about X) - * Roll value should INCREASE as the robot is tipped UP at the left side. (Rotation about Y) - * Yaw value should INCREASE as the robot is rotated Counter Clockwise. (Rotation about Z) - * - * The yaw can be reset (to zero) by pressing the Y button on the gamepad (Triangle on a PS4 controller) - * - * This specific sample DOES NOT assume that the Hub is mounted on one of the three orthogonal - * planes (X/Y, X/Z or Y/Z) OR that the Hub has only been rotated in a range of 90 degree increments. - * - * Note: if your Hub is mounted Orthogonally (on a orthogonal surface, angled at some multiple of - * 90 Degrees) then you should use the simpler SensorIMUOrthogonal sample in this folder. - * - * But... If your Hub is mounted Non-Orthogonally, you must specify one or more rotational angles - * that transform a "Default" Hub orientation into your desired orientation. That is what is - * illustrated here. - * - * Use Android Studio to Copy this Class, and Paste it into your team's code folder with a new name. - * Remove or comment out the @Disabled line to add this OpMode to the Driver Station OpMode list. - * - * Finally, edit this OpMode to use at least one angle around an axis to orient your Hub. - */ -@TeleOp(name = "Sensor: IMU Non-Orthogonal", group = "Sensor") -@Disabled // Comment this out to add to the OpMode list -public class SensorIMUNonOrthogonal extends LinearOpMode -{ - // The IMU sensor object - IMU imu; - - //---------------------------------------------------------------------------------------------- - // Main logic - //---------------------------------------------------------------------------------------------- - - @Override public void runOpMode() throws InterruptedException { - - // Retrieve and initialize the IMU. - // This sample expects the IMU to be in a REV Hub and named "imu". - imu = hardwareMap.get(IMU.class, "imu"); - - /* Define how the hub is mounted to the robot to get the correct Yaw, Pitch and Roll values. - * - * You can apply up to three axis rotations to orient your Hub according to how it's mounted on the robot. - * - * The starting point for these rotations is the "Default" Hub orientation, which is: - * 1) Hub laying flat on a horizontal surface, with the Printed Logo facing UP - * 2) Rotated such that the USB ports are facing forward on the robot. - * - * If you are using a REV External IMU, the "Default" orientation is the same as for a REV Hub, but instead of - * the USB ports facing forward, the I2C port faces forward. - * - * The order that the rotations are performed matters, so this sample shows doing them in the order X, Y, then Z. - * For specifying non-orthogonal hub mounting orientations, we must temporarily use axes - * defined relative to the Hub itself, instead of the usual Robot Coordinate System axes - * used for the results the IMU gives us. In the starting orientation, the Hub axes are - * aligned with the Robot Coordinate System: - * - * X Axis: Starting at Center of Hub, pointing out towards I2C connectors - * Y Axis: Starting at Center of Hub, pointing out towards USB connectors - * Z Axis: Starting at Center of Hub, pointing Up through LOGO - * - * Positive rotation is defined by right-hand rule with thumb pointing in +ve direction on axis. - * - * Some examples. - * - * ---------------------------------------------------------------------------------------------------------------------------------- - * Example A) Assume that the hub is mounted on a sloped plate at the back of the robot, with the USB ports coming out the top of the hub. - * The plate is tilted UP 60 degrees from horizontal. - * - * To get the "Default" hub into this configuration you would just need a single rotation. - * 1) Rotate the Hub +60 degrees around the X axis to tilt up the front edge. - * 2) No rotation around the Y or Z axes. - * - * So the X,Y,Z rotations would be 60,0,0 - * - * ---------------------------------------------------------------------------------------------------------------------------------- - * Example B) Assume that the hub is laying flat on the chassis, but it has been twisted 30 degrees towards the right front wheel to make - * the USB cable accessible. - * - * To get the "Default" hub into this configuration you would just need a single rotation, but around a different axis. - * 1) No rotation around the X or Y axes. - * 2) Rotate the Hub -30 degrees (Clockwise) around the Z axis, since a positive angle would be Counter Clockwise. - * - * So the X,Y,Z rotations would be 0,0,-30 - * - * ---------------------------------------------------------------------------------------------------------------------------------- - * Example C) Assume that the hub is mounted on a vertical plate on the right side of the robot, with the Logo facing out, and the - * Hub rotated so that the USB ports are facing down 30 degrees towards the back wheels of the robot. - * - * To get the "Default" hub into this configuration will require several rotations. - * 1) Rotate the hub +90 degrees around the X axis to get it standing upright with the logo pointing backwards on the robot - * 2) Next, rotate the hub +90 around the Y axis to get it facing to the right. - * 3) Finally rotate the hub +120 degrees around the Z axis to take the USB ports from vertical to sloping down 30 degrees and - * facing towards the back of the robot. - * - * So the X,Y,Z rotations would be 90,90,120 - */ - - // The next three lines define the desired axis rotations. - // To Do: EDIT these values to match YOUR mounting configuration. - double xRotation = 0; // enter the desired X rotation angle here. - double yRotation = 0; // enter the desired Y rotation angle here. - double zRotation = 0; // enter the desired Z rotation angle here. - - Orientation hubRotation = xyzOrientation(xRotation, yRotation, zRotation); - - // Now initialize the IMU with this mounting orientation - RevHubOrientationOnRobot orientationOnRobot = new RevHubOrientationOnRobot(hubRotation); - imu.initialize(new IMU.Parameters(orientationOnRobot)); - - // Loop and update the dashboard - while (!isStopRequested()) { - telemetry.addData("Hub orientation", "X=%.1f, Y=%.1f, Z=%.1f \n", xRotation, yRotation, zRotation); - - // Check to see if heading reset is requested - if (gamepad1.y) { - telemetry.addData("Yaw", "Resetting\n"); - imu.resetYaw(); - } else { - telemetry.addData("Yaw", "Press Y (triangle) on Gamepad to reset\n"); - } - - // Retrieve Rotational Angles and Velocities - YawPitchRollAngles orientation = imu.getRobotYawPitchRollAngles(); - AngularVelocity angularVelocity = imu.getRobotAngularVelocity(AngleUnit.DEGREES); - - telemetry.addData("Yaw (Z)", "%.2f Deg. (Heading)", orientation.getYaw(AngleUnit.DEGREES)); - telemetry.addData("Pitch (X)", "%.2f Deg.", orientation.getPitch(AngleUnit.DEGREES)); - telemetry.addData("Roll (Y)", "%.2f Deg.\n", orientation.getRoll(AngleUnit.DEGREES)); - telemetry.addData("Yaw (Z) velocity", "%.2f Deg/Sec", angularVelocity.zRotationRate); - telemetry.addData("Pitch (X) velocity", "%.2f Deg/Sec", angularVelocity.xRotationRate); - telemetry.addData("Roll (Y) velocity", "%.2f Deg/Sec", angularVelocity.yRotationRate); - telemetry.update(); - } - } -} diff --git a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorIMUOrthogonal.java b/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorIMUOrthogonal.java deleted file mode 100644 index af4c202..0000000 --- a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorIMUOrthogonal.java +++ /dev/null @@ -1,146 +0,0 @@ -/* Copyright (c) 2022 FIRST. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted (subject to the limitations in the disclaimer below) provided that - * the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list - * of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this - * list of conditions and the following disclaimer in the documentation and/or - * other materials provided with the distribution. - * - * Neither the name of FIRST nor the names of its contributors may be used to endorse or - * promote products derived from this software without specific prior written permission. - * - * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS - * LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.firstinspires.ftc.robotcontroller.external.samples; - -import com.qualcomm.hardware.rev.RevHubOrientationOnRobot; -import com.qualcomm.robotcore.eventloop.opmode.Disabled; -import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode; -import com.qualcomm.robotcore.eventloop.opmode.TeleOp; -import com.qualcomm.robotcore.hardware.IMU; -import org.firstinspires.ftc.robotcore.external.navigation.AngleUnit; -import org.firstinspires.ftc.robotcore.external.navigation.AngularVelocity; -import org.firstinspires.ftc.robotcore.external.navigation.YawPitchRollAngles; - -/* - * This OpMode shows how to use the new universal IMU interface. This - * interface may be used with the BNO055 IMU or the BHI260 IMU. It assumes that an IMU is configured - * on the robot with the name "imu". - * - * The sample will display the current Yaw, Pitch and Roll of the robot.
- * With the correct orientation parameters selected, pitch/roll/yaw should act as follows: - * Pitch value should INCREASE as the robot is tipped UP at the front. (Rotation about X)
- * Roll value should INCREASE as the robot is tipped UP at the left side. (Rotation about Y)
- * Yaw value should INCREASE as the robot is rotated Counter Clockwise. (Rotation about Z)
- * - * The yaw can be reset (to zero) by pressing the Y button on the gamepad (Triangle on a PS4 controller) - * - * This specific sample assumes that the Hub is mounted on one of the three orthogonal planes - * (X/Y, X/Z or Y/Z) and that the Hub has only been rotated in a range of 90 degree increments. - * - * Note: if your Hub is mounted on a surface angled at some non-90 Degree multiple (like 30) look at - * the alternative SensorIMUNonOrthogonal sample in this folder. - * - * This "Orthogonal" requirement means that: - * - * 1) The Logo printed on the top of the Hub can ONLY be pointing in one of six directions: - * FORWARD, BACKWARD, UP, DOWN, LEFT and RIGHT. - * - * 2) The USB ports can only be pointing in one of the same six directions:
- * FORWARD, BACKWARD, UP, DOWN, LEFT and RIGHT. - * - * So, To fully define how your Hub is mounted to the robot, you must simply specify:
- * logoFacingDirection
- * usbFacingDirection - * - * Use Android Studio to Copy this Class, and Paste it into your team's code folder with a new name. - * Remove or comment out the @Disabled line to add this OpMode to the Driver Station OpMode list. - * - * Finally, choose the two correct parameters to define how your Hub is mounted and edit this OpMode - * to use those parameters. - */ -@TeleOp(name = "Sensor: IMU Orthogonal", group = "Sensor") -@Disabled // Comment this out to add to the OpMode list -public class SensorIMUOrthogonal extends LinearOpMode -{ - // The IMU sensor object - IMU imu; - - //---------------------------------------------------------------------------------------------- - // Main logic - //---------------------------------------------------------------------------------------------- - - @Override public void runOpMode() throws InterruptedException { - - // Retrieve and initialize the IMU. - // This sample expects the IMU to be in a REV Hub and named "imu". - imu = hardwareMap.get(IMU.class, "imu"); - - /* Define how the hub is mounted on the robot to get the correct Yaw, Pitch and Roll values. - * - * Two input parameters are required to fully specify the Orientation. - * The first parameter specifies the direction the printed logo on the Hub is pointing. - * The second parameter specifies the direction the USB connector on the Hub is pointing. - * All directions are relative to the robot, and left/right is as-viewed from behind the robot. - * - * If you are using a REV 9-Axis IMU, you can use the Rev9AxisImuOrientationOnRobot class instead of the - * RevHubOrientationOnRobot class, which has an I2cPortFacingDirection instead of a UsbFacingDirection. - */ - - /* The next two lines define Hub orientation. - * The Default Orientation (shown) is when a hub is mounted horizontally with the printed logo pointing UP and the USB port pointing FORWARD. - * - * To Do: EDIT these two lines to match YOUR mounting configuration. - */ - RevHubOrientationOnRobot.LogoFacingDirection logoDirection = RevHubOrientationOnRobot.LogoFacingDirection.UP; - RevHubOrientationOnRobot.UsbFacingDirection usbDirection = RevHubOrientationOnRobot.UsbFacingDirection.FORWARD; - - RevHubOrientationOnRobot orientationOnRobot = new RevHubOrientationOnRobot(logoDirection, usbDirection); - - // Now initialize the IMU with this mounting orientation - // Note: if you choose two conflicting directions, this initialization will cause a code exception. - imu.initialize(new IMU.Parameters(orientationOnRobot)); - - // Loop and update the dashboard - while (!isStopRequested()) { - - telemetry.addData("Hub orientation", "Logo=%s USB=%s\n ", logoDirection, usbDirection); - - // Check to see if heading reset is requested - if (gamepad1.y) { - telemetry.addData("Yaw", "Resetting\n"); - imu.resetYaw(); - } else { - telemetry.addData("Yaw", "Press Y (triangle) on Gamepad to reset\n"); - } - - // Retrieve Rotational Angles and Velocities - YawPitchRollAngles orientation = imu.getRobotYawPitchRollAngles(); - AngularVelocity angularVelocity = imu.getRobotAngularVelocity(AngleUnit.DEGREES); - - telemetry.addData("Yaw (Z)", "%.2f Deg. (Heading)", orientation.getYaw(AngleUnit.DEGREES)); - telemetry.addData("Pitch (X)", "%.2f Deg.", orientation.getPitch(AngleUnit.DEGREES)); - telemetry.addData("Roll (Y)", "%.2f Deg.\n", orientation.getRoll(AngleUnit.DEGREES)); - telemetry.addData("Yaw (Z) velocity", "%.2f Deg/Sec", angularVelocity.zRotationRate); - telemetry.addData("Pitch (X) velocity", "%.2f Deg/Sec", angularVelocity.xRotationRate); - telemetry.addData("Roll (Y) velocity", "%.2f Deg/Sec", angularVelocity.yRotationRate); - telemetry.update(); - } - } -} diff --git a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorKLNavxMicro.java b/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorKLNavxMicro.java deleted file mode 100644 index ccc945f..0000000 --- a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorKLNavxMicro.java +++ /dev/null @@ -1,128 +0,0 @@ -/* Copyright (c) 2017 FIRST. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted (subject to the limitations in the disclaimer below) provided that - * the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list - * of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this - * list of conditions and the following disclaimer in the documentation and/or - * other materials provided with the distribution. - * - * Neither the name of FIRST nor the names of its contributors may be used to endorse or - * promote products derived from this software without specific prior written permission. - * - * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS - * LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.firstinspires.ftc.robotcontroller.external.samples; - -import com.qualcomm.hardware.kauailabs.NavxMicroNavigationSensor; -import com.qualcomm.robotcore.eventloop.opmode.Disabled; -import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode; -import com.qualcomm.robotcore.eventloop.opmode.TeleOp; -import com.qualcomm.robotcore.hardware.Gyroscope; -import com.qualcomm.robotcore.hardware.IntegratingGyroscope; -import com.qualcomm.robotcore.util.ElapsedTime; -import org.firstinspires.ftc.robotcore.external.navigation.AngleUnit; -import org.firstinspires.ftc.robotcore.external.navigation.AngularVelocity; -import org.firstinspires.ftc.robotcore.external.navigation.AxesOrder; -import org.firstinspires.ftc.robotcore.external.navigation.AxesReference; -import org.firstinspires.ftc.robotcore.external.navigation.Orientation; - -/* - * This OpMode shows how to use Kauai Labs navX Micro Robotics Navigation - * Sensor. It assumes that the sensor is configured with a name of "navx". - * - * Use Android Studio to Copy this Class, and Paste it into your team's code folder with a new name. - * Remove or comment out the @Disabled line to add this OpMode to the Driver Station OpMode list - */ -@TeleOp(name = "Sensor: KL navX Micro", group = "Sensor") -@Disabled -public class SensorKLNavxMicro extends LinearOpMode { - - /** In this sample, for illustration purposes we use two interfaces on the one gyro object. - * That's likely atypical: you'll probably use one or the other in any given situation, - * depending on what you're trying to do. {@link IntegratingGyroscope} (and it's base interface, - * {@link Gyroscope}) are common interfaces supported by possibly several different gyro - * implementations. {@link NavxMicroNavigationSensor}, by contrast, provides functionality that - * is unique to the navX Micro sensor. - */ - IntegratingGyroscope gyro; - NavxMicroNavigationSensor navxMicro; - - // A timer helps provide feedback while calibration is taking place - ElapsedTime timer = new ElapsedTime(); - - @Override public void runOpMode() throws InterruptedException { - // Get a reference to a Modern Robotics GyroSensor object. We use several interfaces - // on this object to illustrate which interfaces support which functionality. - navxMicro = hardwareMap.get(NavxMicroNavigationSensor.class, "navx"); - gyro = (IntegratingGyroscope)navxMicro; - // If you're only interested int the IntegratingGyroscope interface, the following will suffice. - // gyro = hardwareMap.get(IntegratingGyroscope.class, "navx"); - - // The gyro automatically starts calibrating. This takes a few seconds. - telemetry.log().add("Gyro Calibrating. Do Not Move!"); - - // Wait until the gyro calibration is complete - timer.reset(); - while (navxMicro.isCalibrating()) { - telemetry.addData("calibrating", "%s", Math.round(timer.seconds())%2==0 ? "|.." : "..|"); - telemetry.update(); - Thread.sleep(50); - } - telemetry.log().clear(); telemetry.log().add("Gyro Calibrated. Press Start."); - telemetry.clear(); telemetry.update(); - - // Wait for the start button to be pressed - waitForStart(); - telemetry.log().clear(); - - while (opModeIsActive()) { - - // Read dimensionalized data from the gyro. This gyro can report angular velocities - // about all three axes. Additionally, it internally integrates the Z axis to - // be able to report an absolute angular Z orientation. - AngularVelocity rates = gyro.getAngularVelocity(AngleUnit.DEGREES); - Orientation angles = gyro.getAngularOrientation(AxesReference.INTRINSIC, AxesOrder.ZYX, AngleUnit.DEGREES); - - telemetry.addLine() - .addData("dx", formatRate(rates.xRotationRate)) - .addData("dy", formatRate(rates.yRotationRate)) - .addData("dz", "%s deg/s", formatRate(rates.zRotationRate)); - - telemetry.addLine() - .addData("heading", formatAngle(angles.angleUnit, angles.firstAngle)) - .addData("roll", formatAngle(angles.angleUnit, angles.secondAngle)) - .addData("pitch", "%s deg", formatAngle(angles.angleUnit, angles.thirdAngle)); - telemetry.update(); - - idle(); // Always call idle() at the bottom of your while(opModeIsActive()) loop - } - } - - String formatRate(float rate) { - return String.format("%.3f", rate); - } - - String formatAngle(AngleUnit angleUnit, double angle) { - return formatDegrees(AngleUnit.DEGREES.fromUnit(angleUnit, angle)); - } - - String formatDegrees(double degrees){ - return String.format("%.1f", AngleUnit.DEGREES.normalize(degrees)); - } -} diff --git a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorLimelight3A.java b/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorLimelight3A.java deleted file mode 100644 index 6c1f702..0000000 --- a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorLimelight3A.java +++ /dev/null @@ -1,159 +0,0 @@ -/* -Copyright (c) 2024 Limelight Vision - -All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted (subject to the limitations in the disclaimer below) provided that -the following conditions are met: - -Redistributions of source code must retain the above copyright notice, this list -of conditions and the following disclaimer. - -Redistributions in binary form must reproduce the above copyright notice, this -list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. - -Neither the name of FIRST nor the names of its contributors may be used to -endorse or promote products derived from this software without specific prior -written permission. - -NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS -LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR -TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF -THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ -package org.firstinspires.ftc.robotcontroller.external.samples; - -import com.qualcomm.hardware.limelightvision.LLResult; -import com.qualcomm.hardware.limelightvision.LLResultTypes; -import com.qualcomm.hardware.limelightvision.LLStatus; -import com.qualcomm.hardware.limelightvision.Limelight3A; -import com.qualcomm.robotcore.eventloop.opmode.Disabled; -import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode; -import com.qualcomm.robotcore.eventloop.opmode.TeleOp; - -import org.firstinspires.ftc.robotcore.external.navigation.Pose3D; - -import java.util.List; - -/* - * This OpMode illustrates how to use the Limelight3A Vision Sensor. - * - * @see Limelight - * - * Notes on configuration: - * - * The device presents itself, when plugged into a USB port on a Control Hub as an ethernet - * interface. A DHCP server running on the Limelight automatically assigns the Control Hub an - * ip address for the new ethernet interface. - * - * Since the Limelight is plugged into a USB port, it will be listed on the top level configuration - * activity along with the Control Hub Portal and other USB devices such as webcams. Typically - * serial numbers are displayed below the device's names. In the case of the Limelight device, the - * Control Hub's assigned ip address for that ethernet interface is used as the "serial number". - * - * Tapping the Limelight's name, transitions to a new screen where the user can rename the Limelight - * and specify the Limelight's ip address. Users should take care not to confuse the ip address of - * the Limelight itself, which can be configured through the Limelight settings page via a web browser, - * and the ip address the Limelight device assigned the Control Hub and which is displayed in small text - * below the name of the Limelight on the top level configuration screen. - */ -@TeleOp(name = "Sensor: Limelight3A", group = "Sensor") -@Disabled -public class SensorLimelight3A extends LinearOpMode { - - private Limelight3A limelight; - - @Override - public void runOpMode() throws InterruptedException - { - limelight = hardwareMap.get(Limelight3A.class, "limelight"); - - telemetry.setMsTransmissionInterval(11); - - limelight.pipelineSwitch(0); - - /* - * Starts polling for data. If you neglect to call start(), getLatestResult() will return null. - */ - limelight.start(); - - telemetry.addData(">", "Robot Ready. Press Play."); - telemetry.update(); - waitForStart(); - - while (opModeIsActive()) { - LLStatus status = limelight.getStatus(); - telemetry.addData("Name", "%s", - status.getName()); - telemetry.addData("LL", "Temp: %.1fC, CPU: %.1f%%, FPS: %d", - status.getTemp(), status.getCpu(),(int)status.getFps()); - telemetry.addData("Pipeline", "Index: %d, Type: %s", - status.getPipelineIndex(), status.getPipelineType()); - - LLResult result = limelight.getLatestResult(); - if (result != null) { - // Access general information - Pose3D botpose = result.getBotpose(); - double captureLatency = result.getCaptureLatency(); - double targetingLatency = result.getTargetingLatency(); - double parseLatency = result.getParseLatency(); - telemetry.addData("LL Latency", captureLatency + targetingLatency); - telemetry.addData("Parse Latency", parseLatency); - telemetry.addData("PythonOutput", java.util.Arrays.toString(result.getPythonOutput())); - - if (result.isValid()) { - telemetry.addData("tx", result.getTx()); - telemetry.addData("txnc", result.getTxNC()); - telemetry.addData("ty", result.getTy()); - telemetry.addData("tync", result.getTyNC()); - - telemetry.addData("Botpose", botpose.toString()); - - // Access barcode results - List barcodeResults = result.getBarcodeResults(); - for (LLResultTypes.BarcodeResult br : barcodeResults) { - telemetry.addData("Barcode", "Data: %s", br.getData()); - } - - // Access classifier results - List classifierResults = result.getClassifierResults(); - for (LLResultTypes.ClassifierResult cr : classifierResults) { - telemetry.addData("Classifier", "Class: %s, Confidence: %.2f", cr.getClassName(), cr.getConfidence()); - } - - // Access detector results - List detectorResults = result.getDetectorResults(); - for (LLResultTypes.DetectorResult dr : detectorResults) { - telemetry.addData("Detector", "Class: %s, Area: %.2f", dr.getClassName(), dr.getTargetArea()); - } - - // Access fiducial results - List fiducialResults = result.getFiducialResults(); - for (LLResultTypes.FiducialResult fr : fiducialResults) { - telemetry.addData("Fiducial", "ID: %d, Family: %s, X: %.2f, Y: %.2f", fr.getFiducialId(), fr.getFamily(),fr.getTargetXDegrees(), fr.getTargetYDegrees()); - } - - // Access color results - List colorResults = result.getColorResults(); - for (LLResultTypes.ColorResult cr : colorResults) { - telemetry.addData("Color", "X: %.2f, Y: %.2f", cr.getTargetXDegrees(), cr.getTargetYDegrees()); - } - } - } else { - telemetry.addData("Limelight", "No data available"); - } - - telemetry.update(); - } - limelight.stop(); - } -} diff --git a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorMRColor.java b/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorMRColor.java deleted file mode 100644 index 32b37e0..0000000 --- a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorMRColor.java +++ /dev/null @@ -1,138 +0,0 @@ -/* Copyright (c) 2017 FIRST. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted (subject to the limitations in the disclaimer below) provided that - * the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list - * of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this - * list of conditions and the following disclaimer in the documentation and/or - * other materials provided with the distribution. - * - * Neither the name of FIRST nor the names of its contributors may be used to endorse or - * promote products derived from this software without specific prior written permission. - * - * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS - * LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.firstinspires.ftc.robotcontroller.external.samples; - -import android.app.Activity; -import android.graphics.Color; -import android.view.View; -import com.qualcomm.robotcore.eventloop.opmode.Disabled; -import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode; -import com.qualcomm.robotcore.eventloop.opmode.TeleOp; -import com.qualcomm.robotcore.hardware.ColorSensor; - -/* - * - * This OpMode that shows how to use - * a Modern Robotics Color Sensor. - * - * The OpMode assumes that the color sensor - * is configured with a name of "sensor_color". - * - * You can use the X button on gamepad1 to toggle the LED on and off. - * - * Use Android Studio to Copy this Class, and Paste it into your team's code folder with a new name. - * Remove or comment out the @Disabled line to add this OpMode to the Driver Station OpMode list - */ -@TeleOp(name = "Sensor: MR Color", group = "Sensor") -@Disabled -public class SensorMRColor extends LinearOpMode { - - ColorSensor colorSensor; // Hardware Device Object - - - @Override - public void runOpMode() { - - // hsvValues is an array that will hold the hue, saturation, and value information. - float hsvValues[] = {0F,0F,0F}; - - // values is a reference to the hsvValues array. - final float values[] = hsvValues; - - // get a reference to the RelativeLayout so we can change the background - // color of the Robot Controller app to match the hue detected by the RGB sensor. - int relativeLayoutId = hardwareMap.appContext.getResources().getIdentifier("RelativeLayout", "id", hardwareMap.appContext.getPackageName()); - final View relativeLayout = ((Activity) hardwareMap.appContext).findViewById(relativeLayoutId); - - // bPrevState and bCurrState represent the previous and current state of the button. - boolean bPrevState = false; - boolean bCurrState = false; - - // bLedOn represents the state of the LED. - boolean bLedOn = true; - - // get a reference to our ColorSensor object. - colorSensor = hardwareMap.get(ColorSensor.class, "sensor_color"); - - // Set the LED in the beginning - colorSensor.enableLed(bLedOn); - - // wait for the start button to be pressed. - waitForStart(); - - // while the OpMode is active, loop and read the RGB data. - // Note we use opModeIsActive() as our loop condition because it is an interruptible method. - while (opModeIsActive()) { - - // check the status of the x button on either gamepad. - bCurrState = gamepad1.x; - - // check for button state transitions. - if (bCurrState && (bCurrState != bPrevState)) { - - // button is transitioning to a pressed state. So Toggle LED - bLedOn = !bLedOn; - colorSensor.enableLed(bLedOn); - } - - // update previous state variable. - bPrevState = bCurrState; - - // convert the RGB values to HSV values. - Color.RGBToHSV(colorSensor.red() * 8, colorSensor.green() * 8, colorSensor.blue() * 8, hsvValues); - - // send the info back to driver station using telemetry function. - telemetry.addData("LED", bLedOn ? "On" : "Off"); - telemetry.addData("Clear", colorSensor.alpha()); - telemetry.addData("Red ", colorSensor.red()); - telemetry.addData("Green", colorSensor.green()); - telemetry.addData("Blue ", colorSensor.blue()); - telemetry.addData("Hue", hsvValues[0]); - - // change the background color to match the color detected by the RGB sensor. - // pass a reference to the hue, saturation, and value array as an argument - // to the HSVToColor method. - relativeLayout.post(new Runnable() { - public void run() { - relativeLayout.setBackgroundColor(Color.HSVToColor(0xff, values)); - } - }); - - telemetry.update(); - } - - // Set the panel back to the default color - relativeLayout.post(new Runnable() { - public void run() { - relativeLayout.setBackgroundColor(Color.WHITE); - } - }); - } -} diff --git a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorMRGyro.java b/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorMRGyro.java deleted file mode 100644 index 91c0062..0000000 --- a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorMRGyro.java +++ /dev/null @@ -1,160 +0,0 @@ -/* Copyright (c) 2017 FIRST. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted (subject to the limitations in the disclaimer below) provided that - * the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list - * of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this - * list of conditions and the following disclaimer in the documentation and/or - * other materials provided with the distribution. - * - * Neither the name of FIRST nor the names of its contributors may be used to endorse or - * promote products derived from this software without specific prior written permission. - * - * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS - * LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.firstinspires.ftc.robotcontroller.external.samples; - -import com.qualcomm.hardware.modernrobotics.ModernRoboticsI2cGyro; -import com.qualcomm.robotcore.eventloop.opmode.Disabled; -import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode; -import com.qualcomm.robotcore.eventloop.opmode.TeleOp; -import com.qualcomm.robotcore.hardware.IntegratingGyroscope; -import com.qualcomm.robotcore.util.ElapsedTime; -import org.firstinspires.ftc.robotcore.external.navigation.AngleUnit; -import org.firstinspires.ftc.robotcore.external.navigation.AngularVelocity; -import org.firstinspires.ftc.robotcore.external.navigation.AxesOrder; -import org.firstinspires.ftc.robotcore.external.navigation.AxesReference; - -/* - * This OpMode shows how to use the Modern Robotics Gyro. - * - * The OpMode assumes that the gyro sensor is attached to a Device Interface Module - * I2C channel and is configured with a name of "gyro". - * - * Use Android Studio to Copy this Class, and Paste it into your team's code folder with a new name. - * Remove or comment out the @Disabled line to add this OpMode to the Driver Station OpMode list -*/ -@TeleOp(name = "Sensor: MR Gyro", group = "Sensor") -@Disabled -public class SensorMRGyro extends LinearOpMode { - - /* In this sample, for illustration purposes we use two interfaces on the one gyro object. - * That's likely atypical: you'll probably use one or the other in any given situation, - * depending on what you're trying to do. {@link IntegratingGyroscope} (and it's base interface, - * {@link Gyroscope}) are common interfaces supported by possibly several different gyro - * implementations. {@link ModernRoboticsI2cGyro}, by contrast, provides functionality that - * is unique to the Modern Robotics gyro sensor. - */ - IntegratingGyroscope gyro; - ModernRoboticsI2cGyro modernRoboticsI2cGyro; - - // A timer helps provide feedback while calibration is taking place - ElapsedTime timer = new ElapsedTime(); - - @Override - public void runOpMode() { - - boolean lastResetState = false; - boolean curResetState = false; - - // Get a reference to a Modern Robotics gyro object. We use several interfaces - // on this object to illustrate which interfaces support which functionality. - modernRoboticsI2cGyro = hardwareMap.get(ModernRoboticsI2cGyro.class, "gyro"); - gyro = (IntegratingGyroscope)modernRoboticsI2cGyro; - // If you're only interested int the IntegratingGyroscope interface, the following will suffice. - // gyro = hardwareMap.get(IntegratingGyroscope.class, "gyro"); - // A similar approach will work for the Gyroscope interface, if that's all you need. - - // Start calibrating the gyro. This takes a few seconds and is worth performing - // during the initialization phase at the start of each OpMode. - telemetry.log().add("Gyro Calibrating. Do Not Move!"); - modernRoboticsI2cGyro.calibrate(); - - // Wait until the gyro calibration is complete - timer.reset(); - while (!isStopRequested() && modernRoboticsI2cGyro.isCalibrating()) { - telemetry.addData("calibrating", "%s", Math.round(timer.seconds())%2==0 ? "|.." : "..|"); - telemetry.update(); - sleep(50); - } - - telemetry.log().clear(); telemetry.log().add("Gyro Calibrated. Press Start."); - telemetry.clear(); telemetry.update(); - - // Wait for the start button to be pressed - waitForStart(); - telemetry.log().clear(); - telemetry.log().add("Press A & B to reset heading"); - - // Loop until we're asked to stop - while (opModeIsActive()) { - - // If the A and B buttons are pressed just now, reset Z heading. - curResetState = (gamepad1.a && gamepad1.b); - if (curResetState && !lastResetState) { - modernRoboticsI2cGyro.resetZAxisIntegrator(); - } - lastResetState = curResetState; - - // The raw() methods report the angular rate of change about each of the - // three axes directly as reported by the underlying sensor IC. - int rawX = modernRoboticsI2cGyro.rawX(); - int rawY = modernRoboticsI2cGyro.rawY(); - int rawZ = modernRoboticsI2cGyro.rawZ(); - int heading = modernRoboticsI2cGyro.getHeading(); - int integratedZ = modernRoboticsI2cGyro.getIntegratedZValue(); - - // Read dimensionalized data from the gyro. This gyro can report angular velocities - // about all three axes. Additionally, it internally integrates the Z axis to - // be able to report an absolute angular Z orientation. - AngularVelocity rates = gyro.getAngularVelocity(AngleUnit.DEGREES); - float zAngle = gyro.getAngularOrientation(AxesReference.INTRINSIC, AxesOrder.ZYX, AngleUnit.DEGREES).firstAngle; - - // Read administrative information from the gyro - int zAxisOffset = modernRoboticsI2cGyro.getZAxisOffset(); - int zAxisScalingCoefficient = modernRoboticsI2cGyro.getZAxisScalingCoefficient(); - - telemetry.addLine() - .addData("dx", formatRate(rates.xRotationRate)) - .addData("dy", formatRate(rates.yRotationRate)) - .addData("dz", "%s deg/s", formatRate(rates.zRotationRate)); - telemetry.addData("angle", "%s deg", formatFloat(zAngle)); - telemetry.addData("heading", "%3d deg", heading); - telemetry.addData("integrated Z", "%3d", integratedZ); - telemetry.addLine() - .addData("rawX", formatRaw(rawX)) - .addData("rawY", formatRaw(rawY)) - .addData("rawZ", formatRaw(rawZ)); - telemetry.addLine().addData("z offset", zAxisOffset).addData("z coeff", zAxisScalingCoefficient); - telemetry.update(); - } - } - - String formatRaw(int rawValue) { - return String.format("%d", rawValue); - } - - String formatRate(float rate) { - return String.format("%.3f", rate); - } - - String formatFloat(float rate) { - return String.format("%.3f", rate); - } - -} diff --git a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorMROpticalDistance.java b/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorMROpticalDistance.java deleted file mode 100644 index 6d2dfa3..0000000 --- a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorMROpticalDistance.java +++ /dev/null @@ -1,70 +0,0 @@ -/* Copyright (c) 2017 FIRST. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted (subject to the limitations in the disclaimer below) provided that - * the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list - * of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this - * list of conditions and the following disclaimer in the documentation and/or - * other materials provided with the distribution. - * - * Neither the name of FIRST nor the names of its contributors may be used to endorse or - * promote products derived from this software without specific prior written permission. - * - * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS - * LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.firstinspires.ftc.robotcontroller.external.samples; - -import com.qualcomm.robotcore.eventloop.opmode.Disabled; -import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode; -import com.qualcomm.robotcore.eventloop.opmode.TeleOp; -import com.qualcomm.robotcore.hardware.OpticalDistanceSensor; - -/* - * This OpMode shows how to use a Modern Robotics Optical Distance Sensor - * It assumes that the ODS sensor is configured with a name of "sensor_ods". - * - * Use Android Studio to Copy this Class, and Paste it into your team's code folder with a new name. - * Remove or comment out the @Disabled line to add this OpMode to the Driver Station OpMode list - */ -@TeleOp(name = "Sensor: MR ODS", group = "Sensor") -@Disabled -public class SensorMROpticalDistance extends LinearOpMode { - - OpticalDistanceSensor odsSensor; // Hardware Device Object - - @Override - public void runOpMode() { - - // get a reference to our Light Sensor object. - odsSensor = hardwareMap.get(OpticalDistanceSensor.class, "sensor_ods"); - - // wait for the start button to be pressed. - waitForStart(); - - // while the OpMode is active, loop and read the light levels. - // Note we use opModeIsActive() as our loop condition because it is an interruptible method. - while (opModeIsActive()) { - - // send the info back to driver station using telemetry function. - telemetry.addData("Raw", odsSensor.getRawLightDetected()); - telemetry.addData("Normal", odsSensor.getLightDetected()); - - telemetry.update(); - } - } -} diff --git a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorMRRangeSensor.java b/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorMRRangeSensor.java deleted file mode 100644 index 2039ef9..0000000 --- a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorMRRangeSensor.java +++ /dev/null @@ -1,70 +0,0 @@ -/* Copyright (c) 2017 FIRST. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted (subject to the limitations in the disclaimer below) provided that - * the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list - * of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this - * list of conditions and the following disclaimer in the documentation and/or - * other materials provided with the distribution. - * - * Neither the name of FIRST nor the names of its contributors may be used to endorse or - * promote products derived from this software without specific prior written permission. - * - * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS - * LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.firstinspires.ftc.robotcontroller.external.samples; - -import com.qualcomm.hardware.modernrobotics.ModernRoboticsI2cRangeSensor; -import com.qualcomm.robotcore.eventloop.opmode.Disabled; -import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode; -import com.qualcomm.robotcore.eventloop.opmode.TeleOp; -import org.firstinspires.ftc.robotcore.external.navigation.DistanceUnit; - -/* - * This OpMode illustrates how to use the Modern Robotics Range Sensor. - * - * The OpMode assumes that the range sensor is configured with a name of "sensor_range". - * - * Use Android Studio to Copy this Class, and Paste it into your team's code folder with a new name. - * Remove or comment out the @Disabled line to add this OpMode to the Driver Station OpMode list - * - * @see MR Range Sensor - */ -@TeleOp(name = "Sensor: MR range sensor", group = "Sensor") -@Disabled // comment out or remove this line to enable this OpMode -public class SensorMRRangeSensor extends LinearOpMode { - - ModernRoboticsI2cRangeSensor rangeSensor; - - @Override public void runOpMode() { - - // get a reference to our compass - rangeSensor = hardwareMap.get(ModernRoboticsI2cRangeSensor.class, "sensor_range"); - - // wait for the start button to be pressed - waitForStart(); - - while (opModeIsActive()) { - telemetry.addData("raw ultrasonic", rangeSensor.rawUltrasonic()); - telemetry.addData("raw optical", rangeSensor.rawOptical()); - telemetry.addData("cm optical", "%.2f cm", rangeSensor.cmOptical()); - telemetry.addData("cm", "%.2f cm", rangeSensor.getDistance(DistanceUnit.CM)); - telemetry.update(); - } - } -} diff --git a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorOctoQuad.java b/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorOctoQuad.java deleted file mode 100644 index 8154821..0000000 --- a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorOctoQuad.java +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright (c) 2024 DigitalChickenLabs - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.firstinspires.ftc.robotcontroller.external.samples; - -import com.qualcomm.hardware.digitalchickenlabs.OctoQuad; -import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode; -import com.qualcomm.robotcore.eventloop.opmode.TeleOp; -import com.qualcomm.robotcore.eventloop.opmode.Disabled; -import org.firstinspires.ftc.robotcore.external.Telemetry; - -/* - * This OpMode illustrates how to use the DigitalChickenLabs OctoQuad Quadrature Encoder & Pulse Width Interface Module - * - * The OctoQuad has 8 input channels that can used to read either Relative Quadrature Encoders or Pulse-Width Absolute Encoder inputs. - * Relative Quadrature encoders are found on most FTC motors, and some stand-alone position sensors like the REV Thru-Bore encoder. - * Pulse-Width encoders are less common. The REV Thru-Bore encoder can provide its absolute position via a variable pulse width, - * as can several sonar rangefinders such as the MaxBotix MB1000 series. - * - * This basic sample shows how an OctoQuad can be used to read the position three Odometry pods fitted - * with REV Thru-Bore encoders. For a more advanced example showing additional OctoQuad capabilities, see the SensorOctoQuadAdv sample. - * - * This OpMode assumes that the OctoQuad is attached to an I2C interface named "octoquad" in the robot configuration. - * - * The code assumes the first three OctoQuad inputs are connected as follows - * - Chan 0: for measuring forward motion on the left side of the robot. - * - Chan 1: for measuring forward motion on the right side of the robot. - * - Chan 2: for measuring Lateral (strafing) motion. - * - * The encoder values may be reset to zero by pressing the X (left most) button on Gamepad 1. - * - * This sample does not show how to interpret these readings, just how to obtain and display them. - * - * Use Android Studio to Copy this Class, and Paste it into your team's code folder with a new name. - * Remove or comment out the @Disabled line to add this OpMode to the Driver Station OpMode list - * - * See the sensor's product page: https://www.tindie.com/products/35114/ - */ -@TeleOp(name = "OctoQuad Basic", group="OctoQuad") -@Disabled -public class SensorOctoQuad extends LinearOpMode { - - // Identify which encoder OctoQuad inputs are connected to each odometry pod. - private final int ODO_LEFT = 0; // Facing forward direction on left side of robot (Axial motion) - private final int ODO_RIGHT = 1; // Facing forward direction on right side or robot (Axial motion) - private final int ODO_PERP = 2; // Facing perpendicular direction at the center of the robot (Lateral motion) - - // Declare the OctoQuad object and members to store encoder positions and velocities - private OctoQuad octoquad; - - private int posLeft; - private int posRight; - private int posPerp; - - /** - * This function is executed when this OpMode is selected from the Driver Station. - */ - @Override - public void runOpMode() { - - // Connect to OctoQuad by referring to its name in the Robot Configuration. - octoquad = hardwareMap.get(OctoQuad.class, "octoquad"); - - // Read the Firmware Revision number from the OctoQuad and display it as telemetry. - telemetry.addData("OctoQuad Firmware Version ", octoquad.getFirmwareVersion()); - - // Reverse the count-direction of any encoder that is not what you require. - // e.g. if you push the robot forward and the left encoder counts down, then reverse it so it counts up. - octoquad.setSingleEncoderDirection(ODO_LEFT, OctoQuad.EncoderDirection.REVERSE); - octoquad.setSingleEncoderDirection(ODO_RIGHT, OctoQuad.EncoderDirection.FORWARD); - octoquad.setSingleEncoderDirection(ODO_PERP, OctoQuad.EncoderDirection.FORWARD); - - // Any changes that are made should be saved in FLASH just in case there is a sensor power glitch. - octoquad.saveParametersToFlash(); - - telemetry.addLine("\nPress START to read encoder values"); - telemetry.update(); - - waitForStart(); - - // Configure the telemetry for optimal display of data. - telemetry.setDisplayFormat(Telemetry.DisplayFormat.MONOSPACE); - telemetry.setMsTransmissionInterval(50); - - // Set all the encoder inputs to zero. - octoquad.resetAllPositions(); - - // Loop while displaying the odometry pod positions. - while (opModeIsActive()) { - telemetry.addData(">", "Press X to Reset Encoders\n"); - - // Check for X button to reset encoders. - if (gamepad1.x) { - // Reset the position of all encoders to zero. - octoquad.resetAllPositions(); - } - - // Read all the encoder data. Load into local members. - readOdometryPods(); - - // Display the values. - telemetry.addData("Left ", "%8d counts", posLeft); - telemetry.addData("Right", "%8d counts", posRight); - telemetry.addData("Perp ", "%8d counts", posPerp); - telemetry.update(); - } - } - - private void readOdometryPods() { - // For best performance, we should only perform ONE transaction with the OctoQuad each cycle. - // Since this example only needs to read positions from a few channels, we could use either - // readPositionRange(idxFirst, idxLast) to get a select number of sequential channels - // or - // readAllPositions() to get all 8 encoder readings - // - // Since both calls take almost the same amount of time, and the actual channels may not end up - // being sequential, we will read all of the encoder positions, and then pick out the ones we need. - } -} diff --git a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorOctoQuadAdv.java b/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorOctoQuadAdv.java deleted file mode 100644 index 2e807ce..0000000 --- a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorOctoQuadAdv.java +++ /dev/null @@ -1,277 +0,0 @@ -/* - * Copyright (c) 2024 DigitalChickenLabs - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.firstinspires.ftc.robotcontroller.external.samples; - -import com.qualcomm.hardware.digitalchickenlabs.OctoQuad; -import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode; -import com.qualcomm.robotcore.eventloop.opmode.TeleOp; -import com.qualcomm.robotcore.util.ElapsedTime; -import com.qualcomm.robotcore.util.MovingStatistics; -import com.qualcomm.robotcore.eventloop.opmode.Disabled; - - -import org.firstinspires.ftc.robotcore.external.Telemetry; -import org.firstinspires.ftc.robotcore.external.navigation.AngleUnit; - -import java.util.ArrayList; -import java.util.List; - -/* - * This OpMode illustrates how to use advanced features of the DigitalChickenLabs OctoQuad Quadrature Encoder & Pulse Width Interface Module - * - * The OctoQuad has 8 input channels that can used to read either Quadrature Encoder signals (like with most FTC motors) - * or Pulse-Width style Absolute Encoder inputs (eg: REV Through Bore encoder pulse width output). - * - * This OpMode illustrates several of the more advanced features of an OctoQuad, including Pulse Width measurement and velocity measurement. - * For a more basic OpMode just showing how to read encoder inputs, see the SensorOctoQuad sample. - * - * This OpMode assumes that the OctoQuad is attached to an I2C interface named "octoquad" in the robot configuration. - * - * One system that requires a lot of encoder inputs is a Swerve Drive system. - * Such a drive requires two encoders per drive module: one for position & velocity of the Drive motor/wheel, and one for position/angle of the Steering motor. - * The Drive motor usually requires a quadrature encoder, and the Steering motor requires an Absolute encoder. - * This quantity and combination of encoder inputs is a challenge, but a single OctoQuad could be used to read them all. - * - * This sample will configure an OctoQuad for a swerve drive, as follows - * - Configure 4 Relative Quadrature Encoder inputs and 4 Absolute Pulse-Width Encoder inputs - * - Configure a velocity sample interval of 25 mSec - * - Configure a pulse width input range of 1-1024 uSec for a REV Through Bore Encoder's Absolute Pulse output. - * - * An OctoSwerveDrive class will be created to initialize the OctoQuad, and manage the 4 swerve modules. - * An OctoSwerveModule class will be created to configure and read a single swerve module. - * - * Wiring: - * The OctoQuad will be configured to accept Quadrature encoders on the first four channels and Absolute (pulse width) encoders on the last four channels. - * - * The standard 4-pin to 4-pin cable can be used to connect each Driver Motor encoder to the OctoQuad. (channels 0-3) - * - * A modified version of the REV 6-4 pin cable (shipped with the encoder) connects the steering encoder to the OctoQuad. (channels 4-7) - * To connect the Absolute position signal from a REV Thru-Bore encoder to the OctoQuad, the Provided 6-pin to 4-pin cable will need to be modified. - * At the 6-pin connector end, move the yellow wire from its initial pin-3 position to the ABS pin-5 position. This can be done easily - * by using a small flathead screwdriver to lift the small white tab holding the metal wire crimp in place and gently pulling the wire out. - * See the OctoSwerveDrive() constructor below for the correct wheel/channel assignment. - * - * Use Android Studio to Copy this Class, and Paste it into your team's code folder with a new name. - * Remove or comment out the @Disabled line to add this OpMode to the Driver Station OpMode list - * - * Note: If you prefer, you can move the two support classes from this file, and place them in their own files. - * But leaving them in place is simpler for this example. - * - * See the sensor's product page: https://www.tindie.com/products/35114/ - */ -@TeleOp(name="OctoQuad Advanced", group="OctoQuad") -@Disabled -public class SensorOctoQuadAdv extends LinearOpMode { - - @Override - public void runOpMode() throws InterruptedException { - // Connect to the OctoQuad by looking up its name in the hardwareMap. - OctoQuad octoquad = hardwareMap.get(OctoQuad.class, "octoquad"); - - // Create the interface for the Swerve Drive Encoders - OctoSwerveDrive octoSwerveDrive = new OctoSwerveDrive(octoquad); - - // Display the OctoQuad firmware revision - telemetry.addLine("OctoQuad Firmware v" + octoquad.getFirmwareVersion()); - telemetry.addLine("\nPress START to read encoder values"); - telemetry.update(); - - waitForStart(); - - // Configure the telemetry for optimal display of data. - telemetry.setDisplayFormat(Telemetry.DisplayFormat.MONOSPACE); - telemetry.setMsTransmissionInterval(50); - - // Run stats to determine cycle times. - MovingStatistics avgTime = new MovingStatistics(100); - ElapsedTime elapsedTime = new ElapsedTime(); - - while (opModeIsActive()) { - telemetry.addData(">", "Press X to Reset Encoders\n"); - - if(gamepad1.x) { - octoquad.resetAllPositions(); - } - - // read all the swerve drive encoders and update positions and velocities. - octoSwerveDrive.updateModules(); - octoSwerveDrive.show(telemetry); - - // Update cycle time stats - avgTime.add(elapsedTime.nanoseconds()); - elapsedTime.reset(); - - telemetry.addData("Loop time", "%.1f mS", avgTime.getMean()/1000000); - telemetry.update(); - } - } -} - -// ============================ Internal (Inner) Classes ============================= - -/*** - * OctoSwerveDrive class manages 4 Swerve Modules - * - Performs general OctoQuad initialization - * - Creates 4 module classes, one for each swerve module - * - Updates swerve drive status by reading all data from OctoQuad and Updating each module - * - Displays all swerve drive data as telemetry - */ -class OctoSwerveDrive { - - private final OctoQuad octoquad; - private final List allModules = new ArrayList<>(); - - // members to hold encoder data for each module. - public final OctoSwerveModule LeftFront; - public final OctoSwerveModule RightFront; - public final OctoSwerveModule LeftBack; - public final OctoSwerveModule RightBack; - - // Prepare an object to hold an entire OctoQuad encoder readable register bank (pos and vel) - private final OctoQuad.EncoderDataBlock encoderDataBlock = new OctoQuad.EncoderDataBlock(); - - public OctoSwerveDrive(OctoQuad octoquad) { - this.octoquad = octoquad; - - // Clear out all prior settings and encoder data before setting up desired configuration - octoquad.resetEverything(); - - // Assume first 4 channels are relative encoders and the next 4 are absolute encoders - octoquad.setChannelBankConfig(OctoQuad.ChannelBankConfig.BANK1_QUADRATURE_BANK2_PULSE_WIDTH); - - // Create the four OctoSwerveModules, and add them to a 'list' for easier reference. - - // Note: The wheel/channel order is set here. Ensure your encoder cables match. - // - // Note: The angleOffset must be set for each module so a forward facing wheel shows a steer angle of 0 degrees. - // The process for determining the correct angleOffsets is as follows: - // 1) Set all the angleValues (below) to zero then build and deploy the code. - // 2) Physically rotate all the swerve drives so the wheels are facing forward in the desired 0 degree position - // 3) Run the OpMode, view the telemetry and record the "Degrees" value for each module. - // 4) Update the code by entering the recorded Degrees value for each module as the angleOffset (last) parameter in the lines below. - // - // Rebuild and deploy the new code. Verify that the telemetry now indicates 0 degrees when the wheels are facing forward. - // Also verify that the correct module values change appropriately when you manually spin (drive) and rotate (steer) a wheel. - - allModules.add(LeftFront = new OctoSwerveModule(octoquad, "LF ",0,0));// Drive = 0, Steer = 4 - allModules.add(RightFront = new OctoSwerveModule(octoquad, "RF ",1,0));// Drive = 1, Steer = 5 - allModules.add(LeftBack = new OctoSwerveModule(octoquad, "LB ",2,0));// Drive = 2, Steer = 6 - allModules.add(RightBack = new OctoSwerveModule(octoquad, "RB ",3,0));// Drive = 3, Steer = 7 - - // now make sure the settings persist through any power glitches. - octoquad.saveParametersToFlash(); - } - - /** - * Updates all 4 swerve modules - */ - public void updateModules() { - // Read full OctoQuad data block and then pass DataBlock to each swerve module to update themselves. - octoquad.readAllEncoderData(encoderDataBlock); - for (OctoSwerveModule module : allModules) { - module.updateModule(encoderDataBlock); - } - } - - /** - * Generate telemetry data for all modules. - * @param telemetry OpMode Telemetry object - */ - public void show(Telemetry telemetry) { - // create general header block and then have each module add its own telemetry - telemetry.addData("pos", " Count CPS Degree DPS"); - for (OctoSwerveModule module : allModules) { - module.show(telemetry); - } - } -} - -/*** - * The OctoSwerveModule class manages a single swerve module - */ -class OctoSwerveModule { - - public double driveCounts; - public double driveCountsPerSec; - public double steerDegrees; - public double steerDegreesPerSec; - - private final String name; - private final int channel; - private final double angleOffset; - private final double steerDirMult; - - private static final int VELOCITY_SAMPLE_INTERVAL_MS = 25; // To provide 40 updates per second. - private static final double DEGREES_PER_US = (360.0 / 1024.0); // based on REV Through Bore Encoder - private static final double VELOCITY_SAMPLES_PER_S = (1000.0 / VELOCITY_SAMPLE_INTERVAL_MS); - - // The correct drive and turn directions must be set for the Swerve Module based on the specific hardware geometry. - // Forward motion must generate an increasing drive count. - // Counter Clockwise steer rotation must generate an increasing Steer Angle (degrees) - private static final boolean INVERT_DRIVE_ENCODER = false; // Set true if forward motion decreases drive "Count" - private static final boolean INVERT_STEER_ENCODER = false; // Set true if counter clockwise steer action decreases steer "Degree" - - /*** - * @param octoquad provide access to configure OctoQuad - * @param name name used for telemetry display - * @param quadChannel Quadrature encoder channel. Pulse Width channel is this + 4 - * @param angleOffset Angle to subtract from absolute encoder to calibrate zero position. (see comments above) - */ - public OctoSwerveModule (OctoQuad octoquad, String name, int quadChannel, double angleOffset) { - this.name = name; - this.channel = quadChannel; - this.angleOffset = angleOffset; - this.steerDirMult = INVERT_STEER_ENCODER ? -1 : 1 ; // create a multiplier to flip the steer angle. - - // Set the drive encoder direction. Note the absolute encoder does not have built-in direction inversion. - octoquad.setSingleEncoderDirection(channel, INVERT_DRIVE_ENCODER ? OctoQuad.EncoderDirection.REVERSE : OctoQuad.EncoderDirection.FORWARD); - - // Set the velocity sample interval on both encoders - octoquad.setSingleVelocitySampleInterval(channel, VELOCITY_SAMPLE_INTERVAL_MS); - octoquad.setSingleVelocitySampleInterval(channel + 4, VELOCITY_SAMPLE_INTERVAL_MS); - - // Setup Absolute encoder pulse range to match REV Through Bore encoder. - octoquad.setSingleChannelPulseWidthParams (channel + 4, new OctoQuad.ChannelPulseWidthParams(1,1024)); - } - - /*** - * Calculate the Swerve module's position and velocity values - * @param encoderDataBlock most recent full data block read from OctoQuad. - */ - public void updateModule(OctoQuad.EncoderDataBlock encoderDataBlock) { - driveCounts = encoderDataBlock.positions[channel]; // get Counts. - driveCountsPerSec = encoderDataBlock.velocities[channel] * VELOCITY_SAMPLES_PER_S; // convert counts/interval to counts/sec - - // convert uS to degrees. Add in any possible direction flip. - steerDegrees = AngleUnit.normalizeDegrees((encoderDataBlock.positions[channel+ 4] * DEGREES_PER_US * steerDirMult) - angleOffset); - // convert uS/interval to deg per sec. Add in any possible direction flip. - steerDegreesPerSec = encoderDataBlock.velocities[channel + 4] * DEGREES_PER_US * steerDirMult * VELOCITY_SAMPLES_PER_S; - } - - /** - * Display the Swerve module's state as telemetry - * @param telemetry OpMode Telemetry object - */ - public void show(Telemetry telemetry) { - telemetry.addData(name, "%8.0f %7.0f %7.0f %6.0f", driveCounts, driveCountsPerSec, steerDegrees, steerDegreesPerSec); - } -} diff --git a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorREV2mDistance.java b/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorREV2mDistance.java deleted file mode 100644 index 13883c3..0000000 --- a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorREV2mDistance.java +++ /dev/null @@ -1,87 +0,0 @@ -/* -Copyright (c) 2018 FIRST - -All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted (subject to the limitations in the disclaimer below) provided that -the following conditions are met: - -Redistributions of source code must retain the above copyright notice, this list -of conditions and the following disclaimer. - -Redistributions in binary form must reproduce the above copyright notice, this -list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. - -Neither the name of FIRST nor the names of its contributors may be used to -endorse or promote products derived from this software without specific prior -written permission. - -NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS -LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR -TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF -THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ -package org.firstinspires.ftc.robotcontroller.external.samples; - -import com.qualcomm.hardware.rev.Rev2mDistanceSensor; -import com.qualcomm.robotcore.eventloop.opmode.Disabled; -import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode; -import com.qualcomm.robotcore.eventloop.opmode.TeleOp; -import com.qualcomm.robotcore.hardware.DistanceSensor; -import org.firstinspires.ftc.robotcore.external.navigation.DistanceUnit; - -/* - * This OpMode illustrates how to use the REV Robotics 2M Distance Sensor. - * - * The OpMode assumes that the sensor is configured with a name of "sensor_distance". - * - * Use Android Studio to Copy this Class, and Paste it into your team's code folder with a new name. - * Remove or comment out the @Disabled line to add this OpMode to the Driver Station OpMode list - * - * See the sensor's product page: https://www.revrobotics.com/rev-31-1505/ - */ -@TeleOp(name = "Sensor: REV2mDistance", group = "Sensor") -@Disabled -public class SensorREV2mDistance extends LinearOpMode { - - private DistanceSensor sensorDistance; - - @Override - public void runOpMode() { - // you can use this as a regular DistanceSensor. - sensorDistance = hardwareMap.get(DistanceSensor.class, "sensor_distance"); - - // you can also cast this to a Rev2mDistanceSensor if you want to use added - // methods associated with the Rev2mDistanceSensor class. - Rev2mDistanceSensor sensorTimeOfFlight = (Rev2mDistanceSensor) sensorDistance; - - telemetry.addData(">>", "Press start to continue"); - telemetry.update(); - - waitForStart(); - while(opModeIsActive()) { - // generic DistanceSensor methods. - telemetry.addData("deviceName", sensorDistance.getDeviceName() ); - telemetry.addData("range", String.format("%.01f mm", sensorDistance.getDistance(DistanceUnit.MM))); - telemetry.addData("range", String.format("%.01f cm", sensorDistance.getDistance(DistanceUnit.CM))); - telemetry.addData("range", String.format("%.01f m", sensorDistance.getDistance(DistanceUnit.METER))); - telemetry.addData("range", String.format("%.01f in", sensorDistance.getDistance(DistanceUnit.INCH))); - - // Rev2mDistanceSensor specific methods. - telemetry.addData("ID", String.format("%x", sensorTimeOfFlight.getModelID())); - telemetry.addData("did time out", Boolean.toString(sensorTimeOfFlight.didTimeoutOccur())); - - telemetry.update(); - } - } - -} diff --git a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorSparkFunOTOS.java b/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorSparkFunOTOS.java deleted file mode 100644 index 3a25230..0000000 --- a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorSparkFunOTOS.java +++ /dev/null @@ -1,156 +0,0 @@ -/* - SPDX-License-Identifier: MIT - - Copyright (c) 2024 SparkFun Electronics -*/ -package org.firstinspires.ftc.robotcontroller.external.samples; - -import com.qualcomm.robotcore.eventloop.opmode.Disabled; -import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode; -import com.qualcomm.robotcore.eventloop.opmode.TeleOp; - -import com.qualcomm.hardware.sparkfun.SparkFunOTOS; - -import org.firstinspires.ftc.robotcore.external.navigation.AngleUnit; -import org.firstinspires.ftc.robotcore.external.navigation.DistanceUnit; - -/* - * This OpMode illustrates how to use the SparkFun Qwiic Optical Tracking Odometry Sensor (OTOS) - * - * The OpMode assumes that the sensor is configured with a name of "sensor_otos". - * - * Use Android Studio to Copy this Class, and Paste it into your team's code folder with a new name. - * Remove or comment out the @Disabled line to add this OpMode to the Driver Station OpMode list - * - * See the sensor's product page: https://www.sparkfun.com/products/24904 - */ -@TeleOp(name = "Sensor: SparkFun OTOS", group = "Sensor") -@Disabled -public class SensorSparkFunOTOS extends LinearOpMode { - // Create an instance of the sensor - SparkFunOTOS myOtos; - - @Override - public void runOpMode() throws InterruptedException { - // Get a reference to the sensor - myOtos = hardwareMap.get(SparkFunOTOS.class, "sensor_otos"); - - // All the configuration for the OTOS is done in this helper method, check it out! - configureOtos(); - - // Wait for the start button to be pressed - waitForStart(); - - // Loop until the OpMode ends - while (opModeIsActive()) { - // Get the latest position, which includes the x and y coordinates, plus the - // heading angle - SparkFunOTOS.Pose2D pos = myOtos.getPosition(); - - // Reset the tracking if the user requests it - if (gamepad1.y) { - myOtos.resetTracking(); - } - - // Re-calibrate the IMU if the user requests it - if (gamepad1.x) { - myOtos.calibrateImu(); - } - - // Inform user of available controls - telemetry.addLine("Press Y (triangle) on Gamepad to reset tracking"); - telemetry.addLine("Press X (square) on Gamepad to calibrate the IMU"); - telemetry.addLine(); - - // Log the position to the telemetry - telemetry.addData("X coordinate", pos.x); - telemetry.addData("Y coordinate", pos.y); - telemetry.addData("Heading angle", pos.h); - - // Update the telemetry on the driver station - telemetry.update(); - } - } - - private void configureOtos() { - telemetry.addLine("Configuring OTOS..."); - telemetry.update(); - - // Set the desired units for linear and angular measurements. Can be either - // meters or inches for linear, and radians or degrees for angular. If not - // set, the default is inches and degrees. Note that this setting is not - // persisted in the sensor, so you need to set at the start of all your - // OpModes if using the non-default value. - // myOtos.setLinearUnit(DistanceUnit.METER); - myOtos.setLinearUnit(DistanceUnit.INCH); - // myOtos.setAngularUnit(AnguleUnit.RADIANS); - myOtos.setAngularUnit(AngleUnit.DEGREES); - - // Assuming you've mounted your sensor to a robot and it's not centered, - // you can specify the offset for the sensor relative to the center of the - // robot. The units default to inches and degrees, but if you want to use - // different units, specify them before setting the offset! Note that as of - // firmware version 1.0, these values will be lost after a power cycle, so - // you will need to set them each time you power up the sensor. For example, if - // the sensor is mounted 5 inches to the left (negative X) and 10 inches - // forward (positive Y) of the center of the robot, and mounted 90 degrees - // clockwise (negative rotation) from the robot's orientation, the offset - // would be {-5, 10, -90}. These can be any value, even the angle can be - // tweaked slightly to compensate for imperfect mounting (eg. 1.3 degrees). - SparkFunOTOS.Pose2D offset = new SparkFunOTOS.Pose2D(0, 0, 0); - myOtos.setOffset(offset); - - // Here we can set the linear and angular scalars, which can compensate for - // scaling issues with the sensor measurements. Note that as of firmware - // version 1.0, these values will be lost after a power cycle, so you will - // need to set them each time you power up the sensor. They can be any value - // from 0.872 to 1.127 in increments of 0.001 (0.1%). It is recommended to - // first set both scalars to 1.0, then calibrate the angular scalar, then - // the linear scalar. To calibrate the angular scalar, spin the robot by - // multiple rotations (eg. 10) to get a precise error, then set the scalar - // to the inverse of the error. Remember that the angle wraps from -180 to - // 180 degrees, so for example, if after 10 rotations counterclockwise - // (positive rotation), the sensor reports -15 degrees, the required scalar - // would be 3600/3585 = 1.004. To calibrate the linear scalar, move the - // robot a known distance and measure the error; do this multiple times at - // multiple speeds to get an average, then set the linear scalar to the - // inverse of the error. For example, if you move the robot 100 inches and - // the sensor reports 103 inches, set the linear scalar to 100/103 = 0.971 - myOtos.setLinearScalar(1.0); - myOtos.setAngularScalar(1.0); - - // The IMU on the OTOS includes a gyroscope and accelerometer, which could - // have an offset. Note that as of firmware version 1.0, the calibration - // will be lost after a power cycle; the OTOS performs a quick calibration - // when it powers up, but it is recommended to perform a more thorough - // calibration at the start of all your OpModes. Note that the sensor must - // be completely stationary and flat during calibration! When calling - // calibrateImu(), you can specify the number of samples to take and whether - // to wait until the calibration is complete. If no parameters are provided, - // it will take 255 samples and wait until done; each sample takes about - // 2.4ms, so about 612ms total - myOtos.calibrateImu(); - - // Reset the tracking algorithm - this resets the position to the origin, - // but can also be used to recover from some rare tracking errors - myOtos.resetTracking(); - - // After resetting the tracking, the OTOS will report that the robot is at - // the origin. If your robot does not start at the origin, or you have - // another source of location information (eg. vision odometry), you can set - // the OTOS location to match and it will continue to track from there. - SparkFunOTOS.Pose2D currentPosition = new SparkFunOTOS.Pose2D(0, 0, 0); - myOtos.setPosition(currentPosition); - - // Get the hardware and firmware version - SparkFunOTOS.Version hwVersion = new SparkFunOTOS.Version(); - SparkFunOTOS.Version fwVersion = new SparkFunOTOS.Version(); - myOtos.getVersionInfo(hwVersion, fwVersion); - - telemetry.addLine("OTOS configured! Press start to get position data!"); - telemetry.addLine(); - telemetry.addLine(String.format("OTOS Hardware Version: v%d.%d", hwVersion.major, hwVersion.minor)); - telemetry.addLine(String.format("OTOS Firmware Version: v%d.%d", fwVersion.major, fwVersion.minor)); - telemetry.update(); - } -} diff --git a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorTouch.java b/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorTouch.java deleted file mode 100644 index 3d79447..0000000 --- a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/SensorTouch.java +++ /dev/null @@ -1,78 +0,0 @@ -/* Copyright (c) 2017 FIRST. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted (subject to the limitations in the disclaimer below) provided that - * the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list - * of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this - * list of conditions and the following disclaimer in the documentation and/or - * other materials provided with the distribution. - * - * Neither the name of FIRST nor the names of its contributors may be used to endorse or - * promote products derived from this software without specific prior written permission. - * - * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS - * LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.firstinspires.ftc.robotcontroller.external.samples; - -import com.qualcomm.robotcore.eventloop.opmode.Disabled; -import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode; -import com.qualcomm.robotcore.eventloop.opmode.TeleOp; -import com.qualcomm.robotcore.hardware.TouchSensor; - -/* - * This OpMode demonstrates how to use a REV Robotics Touch Sensor, REV Robotics Magnetic Limit Switch, or other device - * that implements the TouchSensor interface. Any touch sensor that connects its output to ground when pressed - * (known as "active low") can be configured as a "REV Touch Sensor". This includes REV's Magnetic Limit Switch. - * - * The OpMode assumes that the touch sensor is configured with a name of "sensor_touch". - * - * A REV Robotics Touch Sensor must be configured on digital port number 1, 3, 5, or 7. - * A Magnetic Limit Switch can be configured on any digital port. - * - * Use Android Studio to Copy this Class, and Paste it into your team's code folder with a new name. - * Remove or comment out the @Disabled line to add this OpMode to the Driver Station OpMode list. - */ -@TeleOp(name = "Sensor: REV touch sensor", group = "Sensor") -@Disabled -public class SensorTouch extends LinearOpMode { - TouchSensor touchSensor; // Touch sensor Object - - @Override - public void runOpMode() { - - // get a reference to our touchSensor object. - touchSensor = hardwareMap.get(TouchSensor.class, "sensor_touch"); - - // wait for the start button to be pressed. - waitForStart(); - - // while the OpMode is active, loop and read whether the sensor is being pressed. - // Note we use opModeIsActive() as our loop condition because it is an interruptible method. - while (opModeIsActive()) { - - // send the info back to driver station using telemetry function. - if (touchSensor.isPressed()) { - telemetry.addData("Touch Sensor", "Is Pressed"); - } else { - telemetry.addData("Touch Sensor", "Is Not Pressed"); - } - - telemetry.update(); - } - } -} diff --git a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/UtilityCameraFrameCapture.java b/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/UtilityCameraFrameCapture.java deleted file mode 100644 index 69420cc..0000000 --- a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/UtilityCameraFrameCapture.java +++ /dev/null @@ -1,127 +0,0 @@ -/* - * Copyright (c) 2023 FIRST - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted (subject to the limitations in the disclaimer below) provided that - * the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list - * of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this - * list of conditions and the following disclaimer in the documentation and/or - * other materials provided with the distribution. - * - * Neither the name of FIRST nor the names of its contributors may be used to - * endorse or promote products derived from this software without specific prior - * written permission. - * - * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS - * LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR - * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.firstinspires.ftc.robotcontroller.external.samples; - -import android.util.Size; -import com.qualcomm.robotcore.eventloop.opmode.Disabled; -import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode; -import com.qualcomm.robotcore.eventloop.opmode.TeleOp; -import org.firstinspires.ftc.robotcore.external.hardware.camera.BuiltinCameraDirection; -import org.firstinspires.ftc.robotcore.external.hardware.camera.WebcamName; -import org.firstinspires.ftc.vision.VisionPortal; - -import java.util.Locale; - -/* - * This OpMode helps calibrate a webcam or RC phone camera, useful for AprilTag pose estimation - * with the FTC VisionPortal. It captures a camera frame (image) and stores it on the Robot Controller - * (Control Hub or RC phone), with each press of the gamepad button X (or Square). - * Full calibration instructions are here: - * - * https://ftc-docs.firstinspires.org/camera-calibration - * - * In Android Studio, copy this class into your "teamcode" folder with a new name. - * Remove or comment out the @Disabled line to add this OpMode to the Driver Station OpMode list. - * - * In OnBot Java, use "Add File" to add this OpMode from the list of Samples. - */ - -@TeleOp(name = "Utility: Camera Frame Capture", group = "Utility") -@Disabled -public class UtilityCameraFrameCapture extends LinearOpMode -{ - /* - * EDIT THESE PARAMETERS AS NEEDED - */ - final boolean USING_WEBCAM = false; - final BuiltinCameraDirection INTERNAL_CAM_DIR = BuiltinCameraDirection.BACK; - final int RESOLUTION_WIDTH = 640; - final int RESOLUTION_HEIGHT = 480; - - // Internal state - boolean lastX; - int frameCount; - long capReqTime; - - @Override - public void runOpMode() - { - VisionPortal portal; - - if (USING_WEBCAM) - { - portal = new VisionPortal.Builder() - .setCamera(hardwareMap.get(WebcamName.class, "Webcam 1")) - .setCameraResolution(new Size(RESOLUTION_WIDTH, RESOLUTION_HEIGHT)) - .build(); - } - else - { - portal = new VisionPortal.Builder() - .setCamera(INTERNAL_CAM_DIR) - .setCameraResolution(new Size(RESOLUTION_WIDTH, RESOLUTION_HEIGHT)) - .build(); - } - - while (!isStopRequested()) - { - boolean x = gamepad1.x; - - if (x && !lastX) - { - portal.saveNextFrameRaw(String.format(Locale.US, "CameraFrameCapture-%06d", frameCount++)); - capReqTime = System.currentTimeMillis(); - } - - lastX = x; - - telemetry.addLine("######## Camera Capture Utility ########"); - telemetry.addLine(String.format(Locale.US, " > Resolution: %dx%d", RESOLUTION_WIDTH, RESOLUTION_HEIGHT)); - telemetry.addLine(" > Press X (or Square) to capture a frame"); - telemetry.addData(" > Camera Status", portal.getCameraState()); - - if (capReqTime != 0) - { - telemetry.addLine("\nCaptured Frame!"); - } - - if (capReqTime != 0 && System.currentTimeMillis() - capReqTime > 1000) - { - capReqTime = 0; - } - - telemetry.update(); - } - } -} diff --git a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/UtilityOctoQuadConfigMenu.java b/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/UtilityOctoQuadConfigMenu.java deleted file mode 100644 index a962919..0000000 --- a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/UtilityOctoQuadConfigMenu.java +++ /dev/null @@ -1,812 +0,0 @@ -/* - * Copyright (c) 2024 DigitalChickenLabs - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.firstinspires.ftc.robotcontroller.external.samples; - -import com.qualcomm.hardware.digitalchickenlabs.OctoQuad; -import com.qualcomm.robotcore.eventloop.opmode.Disabled; -import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode; -import com.qualcomm.robotcore.eventloop.opmode.TeleOp; -import com.qualcomm.robotcore.hardware.Gamepad; - -import org.firstinspires.ftc.robotcore.external.Telemetry; - -import java.util.ArrayList; -import java.util.Stack; - -/* - * This OpMode illustrates how to use the DigitalChickenLabs OctoQuad Quadrature Encoder & Pulse Width Interface Module. - * - * The OctoQuad has 8 input channels that can used to read either Relative Quadrature Encoders or Pulse-Width Absolute Encoder inputs. - * Relative Quadrature encoders are found on most FTC motors, and some stand-alone position sensors like the REV Thru-Bore encoder. - * Pulse-Width encoders are less common. The REV Thru-Bore encoder can provide its absolute position via a variable pulse width, - * as can several sonar rangefinders such as the MaxBotix MB1000 series. - * - * This OpMode assumes that the OctoQuad is attached to an I2C interface named "octoquad" in the robot configuration. - * - * Remove or comment out the @Disabled line to add this OpMode to the Driver Station OpMode list - * - * Select, Init and run the "OctoQuad Configuration Tool" OpMode - * Read the blue User-Interface tips at the top of the telemetry screen. - * Use the UI buttons to navigate the menu and make any desired changes to the OctoQuad configuration. - * Use the Program Settings To FLASH option to make any changes permanent. - * - * See the sensor's product page: https://www.tindie.com/products/digitalchickenlabs/octoquad-8ch-quadrature-pulse-width-decoder/ - */ -@TeleOp(name = "OctoQuad Configuration Tool", group="OctoQuad") -@Disabled -public class UtilityOctoQuadConfigMenu extends LinearOpMode -{ - TelemetryMenu.MenuElement rootMenu = new TelemetryMenu.MenuElement("OctoQuad Config Menu", true); - TelemetryMenu.MenuElement menuHwInfo = new TelemetryMenu.MenuElement("Hardware Information", false); - TelemetryMenu.EnumOption optionI2cResetMode; - TelemetryMenu.EnumOption optionChannelBankConfig; - - TelemetryMenu.MenuElement menuEncoderDirections = new TelemetryMenu.MenuElement("Set Encoder Directions", false); - TelemetryMenu.BooleanOption[] optionsEncoderDirections = new TelemetryMenu.BooleanOption[OctoQuad.NUM_ENCODERS]; - - TelemetryMenu.MenuElement menuVelocityIntervals = new TelemetryMenu.MenuElement("Velocity Measurement Intervals", false); - TelemetryMenu.IntegerOption[] optionsVelocityIntervals = new TelemetryMenu.IntegerOption[OctoQuad.NUM_ENCODERS]; - - TelemetryMenu.MenuElement menuAbsParams = new TelemetryMenu.MenuElement("Abs. Encoder Pulse Width Params", false); - TelemetryMenu.IntegerOption[] optionsAbsParamsMax = new TelemetryMenu.IntegerOption[OctoQuad.NUM_ENCODERS]; - TelemetryMenu.IntegerOption[] optionsAbsParamsMin = new TelemetryMenu.IntegerOption[OctoQuad.NUM_ENCODERS]; - - TelemetryMenu.OptionElement optionProgramToFlash; - TelemetryMenu.OptionElement optionSendToRAM; - - TelemetryMenu.StaticClickableOption optionExit; - - OctoQuad octoquad; - - boolean error = false; - - @Override - public void runOpMode() - { - octoquad = hardwareMap.getAll(OctoQuad.class).get(0); - - if(octoquad.getChipId() != OctoQuad.OCTOQUAD_CHIP_ID) - { - telemetry.addLine("Error: cannot communicate with OctoQuad. Check your wiring and configuration and try again"); - telemetry.update(); - - error = true; - } - else - { - if(octoquad.getFirmwareVersion().maj != OctoQuad.SUPPORTED_FW_VERSION_MAJ) - { - telemetry.addLine("Error: The OctoQuad is running a different major firmware version than this driver was built for. Cannot run configuration tool"); - telemetry.update(); - - error = true; - } - } - - if(error) - { - waitForStart(); - return; - } - - telemetry.addLine("Retrieving current configuration from OctoQuad"); - telemetry.update(); - - optionExit = new TelemetryMenu.StaticClickableOption("Exit configuration menu") - { - @Override - void onClick() // called on OpMode thread - { - requestOpModeStop(); - } - }; - - optionI2cResetMode = new TelemetryMenu.EnumOption("I2C Reset Mode", OctoQuad.I2cRecoveryMode.values(), octoquad.getI2cRecoveryMode()); - optionChannelBankConfig = new TelemetryMenu.EnumOption("Channel Bank Modes", OctoQuad.ChannelBankConfig.values(), octoquad.getChannelBankConfig()); - - menuHwInfo.addChild(new TelemetryMenu.StaticItem("Board Firmware: v" + octoquad.getFirmwareVersion())); - //menuHwInfo.addChild(new TelemetryMenu.StaticItem("Board unique ID: FIXME")); - - for(int i = 0; i < OctoQuad.NUM_ENCODERS; i++) - { - optionsEncoderDirections[i] = new TelemetryMenu.BooleanOption( - String.format("Encoder %d direction", i), - octoquad.getSingleEncoderDirection(i) == OctoQuad.EncoderDirection.REVERSE, - "-", - "+"); - } - menuEncoderDirections.addChildren(optionsEncoderDirections); - - for(int i = 0; i < OctoQuad.NUM_ENCODERS; i++) - { - optionsVelocityIntervals[i] = new TelemetryMenu.IntegerOption( - String.format("Chan %d velocity intvl", i), - OctoQuad.MIN_VELOCITY_MEASUREMENT_INTERVAL_MS, - OctoQuad.MAX_VELOCITY_MEASUREMENT_INTERVAL_MS, - octoquad.getSingleVelocitySampleInterval(i)); - } - menuVelocityIntervals.addChildren(optionsVelocityIntervals); - - for(int i = 0; i < OctoQuad.NUM_ENCODERS; i++) - { - OctoQuad.ChannelPulseWidthParams params = octoquad.getSingleChannelPulseWidthParams(i); - - optionsAbsParamsMax[i] = new TelemetryMenu.IntegerOption( - String.format("Chan %d max pulse length", i), - OctoQuad.MIN_PULSE_WIDTH_US, - OctoQuad.MAX_PULSE_WIDTH_US, - params.max_length_us); - - optionsAbsParamsMin[i] = new TelemetryMenu.IntegerOption( - String.format("Chan %d min pulse length", i), - OctoQuad.MIN_PULSE_WIDTH_US, - OctoQuad.MAX_PULSE_WIDTH_US, - params.min_length_us); - } - menuAbsParams.addChildren(optionsAbsParamsMin); - menuAbsParams.addChildren(optionsAbsParamsMax); - - optionProgramToFlash = new TelemetryMenu.OptionElement() - { - String name = "Program Settings to FLASH"; - long lastClickTime = 0; - - @Override - protected String getDisplayText() - { - if(lastClickTime == 0) - { - return name; - } - else - { - if(System.currentTimeMillis() - lastClickTime < 1000) - { - return name + " **OK**"; - } - else - { - lastClickTime = 0; - return name; - } - } - } - - @Override - void onClick() - { - sendSettingsToRam(); - octoquad.saveParametersToFlash(); - lastClickTime = System.currentTimeMillis(); - } - }; - - optionSendToRAM = new TelemetryMenu.OptionElement() - { - String name = "Send Settings to RAM"; - long lastClickTime = 0; - - @Override - protected String getDisplayText() - { - if(lastClickTime == 0) - { - return name; - } - else - { - if(System.currentTimeMillis() - lastClickTime < 1000) - { - return name + " **OK**"; - } - else - { - lastClickTime = 0; - return name; - } - } - } - - @Override - void onClick() - { - sendSettingsToRam(); - lastClickTime = System.currentTimeMillis(); - } - }; - - rootMenu.addChild(menuHwInfo); - rootMenu.addChild(optionI2cResetMode); - rootMenu.addChild(optionChannelBankConfig); - rootMenu.addChild(menuEncoderDirections); - rootMenu.addChild(menuVelocityIntervals); - rootMenu.addChild(menuAbsParams); - rootMenu.addChild(optionProgramToFlash); - rootMenu.addChild(optionSendToRAM); - rootMenu.addChild(optionExit); - - TelemetryMenu menu = new TelemetryMenu(telemetry, rootMenu); - - while (!isStopRequested()) - { - menu.loop(gamepad1); - telemetry.update(); - sleep(20); - } - } - - void sendSettingsToRam() - { - for(int i = 0; i < OctoQuad.NUM_ENCODERS; i++) - { - octoquad.setSingleEncoderDirection(i, optionsEncoderDirections[i].getValue() ? OctoQuad.EncoderDirection.REVERSE : OctoQuad.EncoderDirection.FORWARD); - octoquad.setSingleVelocitySampleInterval(i, optionsVelocityIntervals[i].getValue()); - - OctoQuad.ChannelPulseWidthParams params = new OctoQuad.ChannelPulseWidthParams(); - params.max_length_us = optionsAbsParamsMax[i].getValue(); - params.min_length_us = optionsAbsParamsMin[i].getValue(); - - octoquad.setSingleChannelPulseWidthParams(i, params); - } - - octoquad.setI2cRecoveryMode((OctoQuad.I2cRecoveryMode) optionI2cResetMode.getValue()); - octoquad.setChannelBankConfig((OctoQuad.ChannelBankConfig) optionChannelBankConfig.getValue()); - } - - /* - * Copyright (c) 2023 OpenFTC Team - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - - public static class TelemetryMenu - { - private final MenuElement root; - private MenuElement currentLevel; - - private boolean dpadUpPrev; - private boolean dpadDnPrev; - private boolean dpadRightPrev; - private boolean dpadLeftPrev; - private boolean xPrev; - private boolean lbPrev; - - private int selectedIdx = 0; - private Stack selectedIdxStack = new Stack<>(); - - private final Telemetry telemetry; - - /** - * TelemetryMenu constructor - * @param telemetry pass in 'telemetry' from your OpMode - * @param root the root menu element - */ - public TelemetryMenu(Telemetry telemetry, MenuElement root) - { - this.root = root; - this.currentLevel = root; - this.telemetry = telemetry; - - telemetry.setDisplayFormat(Telemetry.DisplayFormat.HTML); - telemetry.setMsTransmissionInterval(50); - } - - /** - * Call this from inside your loop to put the current menu state into - * telemetry, and process gamepad inputs for navigating the menu - * @param gamepad the gamepad you want to use to navigate the menu - */ - public void loop(Gamepad gamepad) - { - // Capture current state of the gamepad buttons we care about; - // We can only look once or we risk a race condition - boolean dpadUp = gamepad.dpad_up; - boolean dpadDn = gamepad.dpad_down; - boolean dpadRight = gamepad.dpad_right; - boolean dpadLeft = gamepad.dpad_left; - boolean x = gamepad.x; - boolean lb = gamepad.left_bumper; - - // Figure out who our children our at this level - // and figure out which item is currently highlighted - // with the selection pointer - ArrayList children = currentLevel.children(); - Element currentSelection = children.get(selectedIdx); - - // Left and right are inputs to the selected item (if it's an Option) - if (currentSelection instanceof OptionElement) - { - if (dpadRight && !dpadRightPrev) // rising edge - { - ((OptionElement) currentSelection).onRightInput(); - } - else if (dpadLeft && !dpadLeftPrev) // rising edge - { - ((OptionElement) currentSelection).onLeftInput(); - } - } - - // Up and down navigate the current selection pointer - if (dpadUp && !dpadUpPrev) // rising edge - { - selectedIdx--; // Move selection pointer up - } - else if (dpadDn && !dpadDnPrev) // rising edge - { - selectedIdx++; // Move selection pointer down - } - - // Make selected index sane (don't let it go out of bounds) :eyes: - if (selectedIdx >= children.size()) - { - selectedIdx = children.size()-1; - } - else if (selectedIdx < 0) - { - selectedIdx = 0; - } - - // Select: either enter submenu or input to option - else if (x && !xPrev) // rising edge - { - // Select up element - if (currentSelection instanceof SpecialUpElement) - { - // We can only go up if we're not at the root level - if (currentLevel != root) - { - // Restore selection pointer to where it was before - selectedIdx = selectedIdxStack.pop(); - - // Change to the parent level - currentLevel = currentLevel.parent(); - } - } - // Input to option - else if (currentSelection instanceof OptionElement) - { - ((OptionElement) currentSelection).onClick(); - } - // Enter submenu - else if (currentSelection instanceof MenuElement) - { - // Save our current selection pointer so we can restore it - // later if the user navigates back up a level - selectedIdxStack.push(selectedIdx); - - // We have no idea what's in the submenu :monkey: so best to - // just set the selection pointer to the first element - selectedIdx = 0; - - // Now the current level becomes the submenu that the selection - // pointer was on - currentLevel = (MenuElement) currentSelection; - } - } - - // Go up a level - else if (lb && !lbPrev) - { - // We can only go up if we're not at the root level - if (currentLevel != root) - { - // Restore selection pointer to where it was before - selectedIdx = selectedIdxStack.pop(); - - // Change to the parent level - currentLevel = currentLevel.parent(); - } - } - - // Save the current button states so that we can look for - // the rising edge the next time around the loop :) - dpadUpPrev = dpadUp; - dpadDnPrev = dpadDn; - dpadRightPrev = dpadRight; - dpadLeftPrev = dpadLeft; - xPrev = x; - lbPrev = lb; - - // Start building the text display. - // First, we add the static directions for gamepad operation - StringBuilder builder = new StringBuilder(); - builder.append(""); - builder.append("Navigate items.....dpad up/down\n") - .append("Select.............X or Square\n") - .append("Edit option........dpad left/right\n") - .append("Up one level.......left bumper\n"); - builder.append(""); - builder.append("\n"); - - // Now actually add the menu options. We start by adding the name of the current menu level. - builder.append(""); - builder.append("Current Menu: ").append(currentLevel.name).append("\n"); - - // Now we loop through all the child elements of this level and add them - for (int i = 0; i < children.size(); i++) - { - // If the selection pointer is at this index, put a green dot in the box :) - if (selectedIdx == i) - { - builder.append("[] "); - } - // Otherwise, just put an empty box - else - { - builder.append("[ ] "); - } - - // Figure out who the selection pointer is pointing at :eyes: - Element e = children.get(i); - - // If it's pointing at a submenu, indicate that it's a submenu to the user - // by prefixing "> " to the name. - if (e instanceof MenuElement) - { - builder.append("> "); - } - - // Finally, add the element's name - builder.append(e.getDisplayText()); - - // We musn't forget the newline - builder.append("\n"); - } - - // Don't forget to close the font tag either - builder.append(""); - - // Build the string!!!! :nerd: - String menu = builder.toString(); - - // Add it to telemetry - telemetry.addLine(menu); - } - - public static class MenuElement extends Element - { - private String name; - private ArrayList children = new ArrayList<>(); - - /** - * Create a new MenuElement; may either be the root menu, or a submenu (set isRoot accordingly) - * @param name the name for this menu - * @param isRoot whether this is a root menu, or a submenu - */ - public MenuElement(String name, boolean isRoot) - { - this.name = name; - - // If it's not the root menu, we add the up one level option as the first element - if (!isRoot) - { - children.add(new SpecialUpElement()); - } - } - - /** - * Add a child element to this menu (may either be an Option or another menu) - * @param child the child element to add - */ - public void addChild(Element child) - { - child.setParent(this); - children.add(child); - } - - /** - * Add multiple child elements to this menu (may either be option, or another menu) - * @param children the children to add - */ - public void addChildren(Element[] children) - { - for (Element e : children) - { - e.setParent(this); - this.children.add(e); - } - } - - @Override - protected String getDisplayText() - { - return name; - } - - private ArrayList children() - { - return children; - } - } - - public static abstract class OptionElement extends Element - { - /** - * Override this to get notified when the element is clicked - */ - void onClick() {} - - /** - * Override this to get notified when the element gets a "left edit" input - */ - protected void onLeftInput() {} - - /** - * Override this to get notified when the element gets a "right edit" input - */ - protected void onRightInput() {} - } - - public static class EnumOption extends OptionElement - { - protected int idx = 0; - protected Enum[] e; - protected String name; - - public EnumOption(String name, Enum[] e) - { - this.e = e; - this.name = name; - } - - public EnumOption(String name, Enum[] e, Enum def) - { - this(name, e); - idx = def.ordinal(); - } - - @Override - public void onLeftInput() - { - idx++; - - if(idx > e.length-1) - { - idx = 0; - } - } - - @Override - public void onRightInput() - { - idx--; - - if(idx < 0) - { - idx = e.length-1; - } - } - - @Override - public void onClick() - { - //onRightInput(); - } - - @Override - protected String getDisplayText() - { - return String.format("%s: %s", name, e[idx].name()); - } - - public Enum getValue() - { - return e[idx]; - } - } - - public static class IntegerOption extends OptionElement - { - protected int i; - protected int min; - protected int max; - protected String name; - - public IntegerOption(String name, int min, int max, int def) - { - this.name = name; - this.min = min; - this.max = max; - this.i = def; - } - - @Override - public void onLeftInput() - { - i--; - - if(i < min) - { - i = max; - } - } - - @Override - public void onRightInput() - { - i++; - - if(i > max) - { - i = min; - } - } - - @Override - public void onClick() - { - //onRightInput(); - } - - @Override - protected String getDisplayText() - { - return String.format("%s: %d", name, i); - } - - public int getValue() - { - return i; - } - } - - static class BooleanOption extends OptionElement - { - private String name; - private boolean val = true; - - private String customTrue; - private String customFalse; - - BooleanOption(String name, boolean def) - { - this.name = name; - this.val = def; - } - - BooleanOption(String name, boolean def, String customTrue, String customFalse) - { - this(name, def); - this.customTrue = customTrue; - this.customFalse = customFalse; - } - - @Override - public void onLeftInput() - { - val = !val; - } - - @Override - public void onRightInput() - { - val = !val; - } - - @Override - public void onClick() - { - val = !val; - } - - @Override - protected String getDisplayText() - { - String valStr; - - if(customTrue != null && customFalse != null) - { - valStr = val ? customTrue : customFalse; - } - else - { - valStr = val ? "true" : "false"; - } - - return String.format("%s: %s", name, valStr); - } - - public boolean getValue() - { - return val; - } - } - - /** - * - */ - public static class StaticItem extends OptionElement - { - private String name; - - public StaticItem(String name) - { - this.name = name; - } - - @Override - protected String getDisplayText() - { - return name; - } - } - - public static abstract class StaticClickableOption extends OptionElement - { - private String name; - - public StaticClickableOption(String name) - { - this.name = name; - } - - abstract void onClick(); - - @Override - protected String getDisplayText() - { - return name; - } - } - - private static abstract class Element - { - private MenuElement parent; - - protected void setParent(MenuElement parent) - { - this.parent = parent; - } - - protected MenuElement parent() - { - return parent; - } - - protected abstract String getDisplayText(); - } - - private static class SpecialUpElement extends Element - { - @Override - protected String getDisplayText() - { - return ".. ↰ Up One Level"; - } - } - } -} diff --git a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/readme.md b/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/readme.md deleted file mode 100644 index 326978d..0000000 --- a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/readme.md +++ /dev/null @@ -1,45 +0,0 @@ - -## Caution -No Team-specific code should be placed or modified in this ``.../samples`` folder. - -Samples should be Copied from here, and then Pasted into the team's -[/TeamCode/src/main/java/org/firstinspires/ftc/teamcode](../../../../../../../../../../TeamCode/src/main/java/org/firstinspires/ftc/teamcode) - folder, using the Android Studio cut and paste commands. This automatically changes all file and -class names to be consistent. From there, the sample can be modified to suit the team's needs. - -For more detailed instructions see the /teamcode readme. - -### Naming of Samples - -To gain a better understanding of how the samples are organized, and how to interpret the -naming system, it will help to understand the conventions that were used during their creation. - -These conventions are described (in detail) in the sample_conventions.md file in this folder. - -To summarize: A range of different samples classes will reside in the java/external/samples. -The class names will follow a naming convention which indicates the purpose of each class. -The prefix of the name will be one of the following: - -Basic: This is a minimally functional OpMode used to illustrate the skeleton/structure - of a particular style of OpMode. These are bare bones examples. - -Sensor: This is a Sample OpMode that shows how to use a specific sensor. - It is not intended to drive a functioning robot, it is simply showing the minimal code - required to read and display the sensor values. - -Robot: This is a Sample OpMode that assumes a simple two-motor (differential) drive base. - It may be used to provide a common baseline driving OpMode, or - to demonstrate how a particular sensor or concept can be used to navigate. - -Concept: This is a sample OpMode that illustrates performing a specific function or concept. - These may be complex, but their operation should be explained clearly in the comments, - or the comments should reference an external doc, guide or tutorial. - Each OpMode should try to only demonstrate a single concept so they are easy to - locate based on their name. These OpModes may not produce a drivable robot. - -After the prefix, other conventions will apply: - -* Sensor class names are constructed as: Sensor - Company - Type -* Robot class names are constructed as: Robot - Mode - Action - OpModetype -* Concept class names are constructed as: Concept - Topic - OpModetype - diff --git a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/sample_conventions.md b/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/sample_conventions.md deleted file mode 100644 index e85e625..0000000 --- a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/sample_conventions.md +++ /dev/null @@ -1,113 +0,0 @@ -## Sample Class/Opmode conventions -#### V 1.1.0 8/9/2017 - -This document defines the FTC Sample OpMode and Class conventions. - -### OpMode Name - -To gain a better understanding of how the samples are organized, and how to interpret the -naming system, it will help to understand the conventions that were used during their creation. - -To summarize: A range of different samples classes will reside in the java/external/samples. -The class names will follow a naming convention which indicates the purpose of each class. -The prefix of the name will be one of the following: - -Basic: This is a minimally functional OpMode used to illustrate the skeleton/structure - of a particular style of OpMode. These are bare bones examples. - -Sensor: This is a Sample OpMode that shows how to use a specific sensor. - It is not intended to drive a functioning robot, it is simply showing the minimal code - required to read and display the sensor values. - -Robot: This is a Sample OpMode that assumes a simple two-motor (differential) drive base. - It may be used to provide a common baseline driving OpMode, or - to demonstrate how a particular sensor or concept can be used to navigate. - -Concept: This is a sample OpMode that illustrates performing a specific function or concept. - These may be complex, but their operation should be explained clearly in the comments, - or the comments should reference an external doc, guide or tutorial. - Each OpMode should try to only demonstrate a single concept so they are easy to - locate based on their name. These OpModes may not produce a drivable robot. - -Utility: This sample type is provided as a useful tool, or aide, to perform some specific development task. - It is not expected to be something you would include in your own robot code. - To use the tool, comment out the @Disabled annotation and build the App. - Read the comments found in the sample for an explanation of its intended use. - -After the prefix, other conventions will apply: - -* Sensor class names should constructed as: Sensor - Company - Type -* Robot class names should be constructed as: Robot - Mode - Action - OpModetype -* Concept class names should be constructed as: Concept - Topic - OpModetype - -### Sample OpMode Content/Style - -Code is formatted as per the Google Style Guide: - -https://google.github.io/styleguide/javaguide.html - -With “Sensor” and “Hardware” samples, the code should demonstrate the essential function, -and not be embellished with too much additional “clever” code. If a sensor has special -addressing needs, or has a variety of modes or outputs, these should be demonstrated as -simply as possible. - -Special programming methods, or robot control techniques should be reserved for “Concept” Samples, -and where possible, Samples should strive to only demonstrate a single concept, -eg: State machine coding, or a User Menu system, and not combine them into a single “all inclusive” -sample. This will prevent an “all inclusive” Sample being deleted just because one part of it -becomes obsolete. - -### Device Configuration Names - -The following device names are used in the external samples - -** Motors: -left_drive -right_drive -left_arm - -** Servos: -left_hand -right_hand -arm -claw - -** Sensors: -sensor_color -sensor_ir -sensor_light -sensor_ods -sensor_range -sensor_touch -sensor_color_distance -sensor_digital -digin -digout - -** Localization: -compass -gyro -imu -navx - -### Device Object Names - -Device Object names should use the same words as the device’s configuration name, but they -should be re-structured to be a suitable Java variable name. This should keep the same word order, -but adopt the style of beginning with a lower case letter, and then each subsequent word -starting with an upper case letter. - -Eg: from the examples above: tool, leftMotor, rightClawServo, rearLightSensor. - -Note: Sometimes it’s helpful to put the device type first, followed by the variant. -eg: motorLeft and motorRight, but this should only be done if the same word order -is used on the device configuration name. - -### OpMode code Comments - -Sample comments should read like normal code comments, that is, as an explanation of what the -sample code is doing. They should NOT be directives to the user, -like: “insert your joystick code here” as these comments typically aren’t -detailed enough to be useful. They also often get left in the code and become garbage. - -Instead, an example of the joystick code should be shown with a comment describing what it is doing. diff --git a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/internal/FtcOpModeRegister.java b/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/internal/FtcOpModeRegister.java deleted file mode 100644 index ceab67d..0000000 --- a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/internal/FtcOpModeRegister.java +++ /dev/null @@ -1,68 +0,0 @@ -/* Copyright (c) 2014, 2015 Qualcomm Technologies Inc - -All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted (subject to the limitations in the disclaimer below) provided that -the following conditions are met: - -Redistributions of source code must retain the above copyright notice, this list -of conditions and the following disclaimer. - -Redistributions in binary form must reproduce the above copyright notice, this -list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. - -Neither the name of Qualcomm Technologies Inc nor the names of its contributors -may be used to endorse or promote products derived from this software without -specific prior written permission. - -NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS -LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ - -package org.firstinspires.ftc.robotcontroller.internal; - -import com.qualcomm.robotcore.eventloop.opmode.OpModeManager; -import com.qualcomm.robotcore.eventloop.opmode.OpModeRegister; - -/** - * {@link FtcOpModeRegister} is responsible for registering OpModes for use in an FTC game. - * @see #register(OpModeManager) - */ -public class FtcOpModeRegister implements OpModeRegister { - - /** - * {@link #register(OpModeManager)} is called by the SDK game in order to register - * OpMode classes or instances that will participate in an FTC game. - * - * There are two mechanisms by which an OpMode may be registered. - * - * 1) The preferred method is by means of class annotations in the OpMode itself. - * See, for example the class annotations in {@link org.firstinspires.ftc.robotcontroller.external.samples.ConceptNullOp}. - * - * 2) The other, retired, method is to modify this {@link #register(OpModeManager)} - * method to include explicit calls to OpModeManager.register(). - * This method of modifying this file directly is discouraged, as it - * makes updates to the SDK harder to integrate into your code. - * - * @param manager the object which contains methods for carrying out OpMode registrations - * - * @see com.qualcomm.robotcore.eventloop.opmode.TeleOp - * @see com.qualcomm.robotcore.eventloop.opmode.Autonomous - */ - public void register(OpModeManager manager) { - - /** - * Any manual OpMode class registrations should go here. - */ - } -} diff --git a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/internal/FtcRobotControllerActivity.java b/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/internal/FtcRobotControllerActivity.java deleted file mode 100644 index 3f1f77c..0000000 --- a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/internal/FtcRobotControllerActivity.java +++ /dev/null @@ -1,845 +0,0 @@ -/* Copyright (c) 2014, 2015 Qualcomm Technologies Inc - -All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted (subject to the limitations in the disclaimer below) provided that -the following conditions are met: - -Redistributions of source code must retain the above copyright notice, this list -of conditions and the following disclaimer. - -Redistributions in binary form must reproduce the above copyright notice, this -list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. - -Neither the name of Qualcomm Technologies Inc nor the names of its contributors -may be used to endorse or promote products derived from this software without -specific prior written permission. - -NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS -LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ - -package org.firstinspires.ftc.robotcontroller.internal; - -import android.app.ActionBar; -import android.app.Activity; -import android.app.ActivityManager; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.ServiceConnection; -import android.content.SharedPreferences; -import android.content.res.Configuration; -import android.hardware.usb.UsbDevice; -import android.hardware.usb.UsbManager; -import android.net.wifi.WifiManager; -import android.os.Bundle; -import android.os.IBinder; -import android.preference.PreferenceManager; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.StringRes; -import android.view.Menu; -import android.view.MenuItem; -import android.view.MotionEvent; -import android.view.View; -import android.view.WindowManager; -import android.webkit.WebView; -import android.widget.ImageButton; -import android.widget.LinearLayout; -import android.widget.LinearLayout.LayoutParams; -import android.widget.PopupMenu; -import android.widget.TextView; - -import com.google.blocks.ftcrobotcontroller.ProgrammingWebHandlers; -import com.google.blocks.ftcrobotcontroller.runtime.BlocksOpMode; -import com.qualcomm.ftccommon.ClassManagerFactory; -import com.qualcomm.ftccommon.FtcAboutActivity; -import com.qualcomm.ftccommon.FtcEventLoop; -import com.qualcomm.ftccommon.FtcEventLoopIdle; -import com.qualcomm.ftccommon.FtcRobotControllerService; -import com.qualcomm.ftccommon.FtcRobotControllerService.FtcRobotControllerBinder; -import com.qualcomm.ftccommon.FtcRobotControllerSettingsActivity; -import com.qualcomm.ftccommon.LaunchActivityConstantsList; -import com.qualcomm.ftccommon.LaunchActivityConstantsList.RequestCode; -import com.qualcomm.ftccommon.Restarter; -import com.qualcomm.ftccommon.UpdateUI; -import com.qualcomm.ftccommon.configuration.EditParameters; -import com.qualcomm.ftccommon.configuration.FtcLoadFileActivity; -import com.qualcomm.ftccommon.configuration.RobotConfigFile; -import com.qualcomm.ftccommon.configuration.RobotConfigFileManager; -import com.qualcomm.ftcrobotcontroller.BuildConfig; -import com.qualcomm.ftcrobotcontroller.R; -import com.qualcomm.hardware.HardwareFactory; -import com.qualcomm.robotcore.eventloop.EventLoopManager; -import com.qualcomm.robotcore.eventloop.opmode.FtcRobotControllerServiceState; -import com.qualcomm.robotcore.eventloop.opmode.OpModeRegister; -import com.qualcomm.robotcore.hardware.configuration.LynxConstants; -import com.qualcomm.robotcore.hardware.configuration.Utility; -import com.qualcomm.robotcore.robot.Robot; -import com.qualcomm.robotcore.robot.RobotState; -import com.qualcomm.robotcore.util.ClockWarningSource; -import com.qualcomm.robotcore.util.Device; -import com.qualcomm.robotcore.util.Dimmer; -import com.qualcomm.robotcore.util.ImmersiveMode; -import com.qualcomm.robotcore.util.RobotLog; -import com.qualcomm.robotcore.util.WebServer; -import com.qualcomm.robotcore.wifi.NetworkConnection; -import com.qualcomm.robotcore.wifi.NetworkConnectionFactory; -import com.qualcomm.robotcore.wifi.NetworkType; - -import org.firstinspires.ftc.ftccommon.external.SoundPlayingRobotMonitor; -import org.firstinspires.ftc.ftccommon.internal.AnnotatedHooksClassFilter; -import org.firstinspires.ftc.ftccommon.internal.FtcRobotControllerWatchdogService; -import org.firstinspires.ftc.ftccommon.internal.ProgramAndManageActivity; -import org.firstinspires.ftc.onbotjava.ExternalLibraries; -import org.firstinspires.ftc.onbotjava.OnBotJavaHelperImpl; -import org.firstinspires.ftc.onbotjava.OnBotJavaProgrammingMode; -import org.firstinspires.ftc.robotcore.external.navigation.MotionDetection; -import org.firstinspires.ftc.robotcore.internal.hardware.android.AndroidBoard; -import org.firstinspires.ftc.robotcore.internal.network.DeviceNameManagerFactory; -import org.firstinspires.ftc.robotcore.internal.network.PreferenceRemoterRC; -import org.firstinspires.ftc.robotcore.internal.network.StartResult; -import org.firstinspires.ftc.robotcore.internal.network.WifiDirectChannelChanger; -import org.firstinspires.ftc.robotcore.internal.network.WifiMuteEvent; -import org.firstinspires.ftc.robotcore.internal.network.WifiMuteStateMachine; -import org.firstinspires.ftc.robotcore.internal.opmode.ClassManager; -import org.firstinspires.ftc.robotcore.internal.opmode.OnBotJavaHelper; -import org.firstinspires.ftc.robotcore.internal.system.AppAliveNotifier; -import org.firstinspires.ftc.robotcore.internal.system.AppUtil; -import org.firstinspires.ftc.robotcore.internal.system.Assert; -import org.firstinspires.ftc.robotcore.internal.system.PreferencesHelper; -import org.firstinspires.ftc.robotcore.internal.system.ServiceController; -import org.firstinspires.ftc.robotcore.internal.ui.ThemedActivity; -import org.firstinspires.ftc.robotcore.internal.ui.UILocation; -import org.firstinspires.ftc.robotcore.internal.webserver.RobotControllerWebInfo; -import org.firstinspires.ftc.robotserver.internal.programmingmode.ProgrammingModeManager; -import org.firstinspires.inspection.RcInspectionActivity; -import org.threeten.bp.YearMonth; -import org.xmlpull.v1.XmlPullParserException; - -import java.io.FileNotFoundException; -import java.util.List; -import java.util.Queue; -import java.util.concurrent.ConcurrentLinkedQueue; - -@SuppressWarnings("WeakerAccess") -public class FtcRobotControllerActivity extends Activity - { - public static final String TAG = "RCActivity"; - public String getTag() { return TAG; } - - private static final int REQUEST_CONFIG_WIFI_CHANNEL = 1; - private static final int NUM_GAMEPADS = 2; - - protected WifiManager.WifiLock wifiLock; - protected RobotConfigFileManager cfgFileMgr; - - private OnBotJavaHelper onBotJavaHelper; - - protected ProgrammingModeManager programmingModeManager; - - protected UpdateUI.Callback callback; - protected Context context; - protected Utility utility; - protected StartResult prefRemoterStartResult = new StartResult(); - protected StartResult deviceNameStartResult = new StartResult(); - protected PreferencesHelper preferencesHelper; - protected final SharedPreferencesListener sharedPreferencesListener = new SharedPreferencesListener(); - - protected ImageButton buttonMenu; - protected TextView textDeviceName; - protected TextView textNetworkConnectionStatus; - protected TextView textRobotStatus; - protected TextView[] textGamepad = new TextView[NUM_GAMEPADS]; - protected TextView textOpMode; - protected TextView textErrorMessage; - protected ImmersiveMode immersion; - - protected UpdateUI updateUI; - protected Dimmer dimmer; - protected LinearLayout entireScreenLayout; - - protected FtcRobotControllerService controllerService; - protected NetworkType networkType; - - protected FtcEventLoop eventLoop; - protected Queue receivedUsbAttachmentNotifications; - - protected WifiMuteStateMachine wifiMuteStateMachine; - protected MotionDetection motionDetection; - - private static boolean permissionsValidated = false; - - private WifiDirectChannelChanger wifiDirectChannelChanger; - - protected class RobotRestarter implements Restarter { - - public void requestRestart() { - requestRobotRestart(); - } - - } - - protected boolean serviceShouldUnbind = false; - protected ServiceConnection connection = new ServiceConnection() { - @Override - public void onServiceConnected(ComponentName name, IBinder service) { - FtcRobotControllerBinder binder = (FtcRobotControllerBinder) service; - onServiceBind(binder.getService()); - } - - @Override - public void onServiceDisconnected(ComponentName name) { - RobotLog.vv(FtcRobotControllerService.TAG, "%s.controllerService=null", TAG); - controllerService = null; - } - }; - - @Override - protected void onNewIntent(Intent intent) { - super.onNewIntent(intent); - - if (UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(intent.getAction())) { - UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); - RobotLog.vv(TAG, "ACTION_USB_DEVICE_ATTACHED: %s", usbDevice.getDeviceName()); - - if (usbDevice != null) { // paranoia - // We might get attachment notifications before the event loop is set up, so - // we hold on to them and pass them along only when we're good and ready. - if (receivedUsbAttachmentNotifications != null) { // *total* paranoia - receivedUsbAttachmentNotifications.add(usbDevice); - passReceivedUsbAttachmentsToEventLoop(); - } - } - } - } - - protected void passReceivedUsbAttachmentsToEventLoop() { - if (this.eventLoop != null) { - for (;;) { - UsbDevice usbDevice = receivedUsbAttachmentNotifications.poll(); - if (usbDevice == null) - break; - this.eventLoop.onUsbDeviceAttached(usbDevice); - } - } - else { - // Paranoia: we don't want the pending list to grow without bound when we don't - // (yet) have an event loop - while (receivedUsbAttachmentNotifications.size() > 100) { - receivedUsbAttachmentNotifications.poll(); - } - } - } - - /** - * There are cases where a permission may be revoked and the system restart will restart the - * FtcRobotControllerActivity, instead of the launch activity. Detect when that happens, and throw - * the device back to the permission validator activity. - */ - protected boolean enforcePermissionValidator() { - if (!permissionsValidated) { - RobotLog.vv(TAG, "Redirecting to permission validator"); - Intent permissionValidatorIntent = new Intent(AppUtil.getDefContext(), PermissionValidatorWrapper.class); - startActivity(permissionValidatorIntent); - finish(); - return true; - } else { - RobotLog.vv(TAG, "Permissions validated already"); - return false; - } - } - - public static void setPermissionsValidated() { - permissionsValidated = true; - } - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - if (enforcePermissionValidator()) { - return; - } - - RobotLog.onApplicationStart(); // robustify against onCreate() following onDestroy() but using the same app instance, which apparently does happen - RobotLog.vv(TAG, "onCreate()"); - ThemedActivity.appAppThemeToActivity(getTag(), this); // do this way instead of inherit to help AppInventor - - // Oddly, sometimes after a crash & restart the root activity will be something unexpected, like from the before crash? We don't yet understand - RobotLog.vv(TAG, "rootActivity is of class %s", AppUtil.getInstance().getRootActivity().getClass().getSimpleName()); - RobotLog.vv(TAG, "launchActivity is of class %s", FtcRobotControllerWatchdogService.launchActivity()); - Assert.assertTrue(FtcRobotControllerWatchdogService.isLaunchActivity(AppUtil.getInstance().getRootActivity())); - Assert.assertTrue(AppUtil.getInstance().isRobotController()); - - // Quick check: should we pretend we're not here, and so allow the Lynx to operate as - // a stand-alone USB-connected module? - if (LynxConstants.isRevControlHub()) { - // Double-sure check that we can talk to the DB over the serial TTY - AndroidBoard.getInstance().getAndroidBoardIsPresentPin().setState(true); - } - - context = this; - utility = new Utility(this); - - DeviceNameManagerFactory.getInstance().start(deviceNameStartResult); - - PreferenceRemoterRC.getInstance().start(prefRemoterStartResult); - - receivedUsbAttachmentNotifications = new ConcurrentLinkedQueue(); - eventLoop = null; - - setContentView(R.layout.activity_ftc_controller); - - preferencesHelper = new PreferencesHelper(TAG, context); - preferencesHelper.writeBooleanPrefIfDifferent(context.getString(R.string.pref_rc_connected), true); - preferencesHelper.getSharedPreferences().registerOnSharedPreferenceChangeListener(sharedPreferencesListener); - - // Check if this RC app is from a later FTC season than what was installed previously - int ftcSeasonYearOfPreviouslyInstalledRc = preferencesHelper.readInt(getString(R.string.pref_ftc_season_year_of_current_rc), 0); - int ftcSeasonYearOfCurrentlyInstalledRc = AppUtil.getInstance().getFtcSeasonYear(AppUtil.getInstance().getLocalSdkBuildMonth()).getValue(); - if (ftcSeasonYearOfCurrentlyInstalledRc > ftcSeasonYearOfPreviouslyInstalledRc) { - preferencesHelper.writeIntPrefIfDifferent(getString(R.string.pref_ftc_season_year_of_current_rc), ftcSeasonYearOfCurrentlyInstalledRc); - // Since it's a new FTC season, we should reset certain settings back to their default values. - preferencesHelper.writeBooleanPrefIfDifferent(getString(R.string.pref_warn_about_2_4_ghz_band), true); - preferencesHelper.writeBooleanPrefIfDifferent(getString(R.string.pref_warn_about_obsolete_software), true); - preferencesHelper.writeBooleanPrefIfDifferent(getString(R.string.pref_warn_about_mismatched_app_versions), true); - preferencesHelper.writeBooleanPrefIfDifferent(getString(R.string.pref_warn_about_incorrect_clocks), true); - } - - entireScreenLayout = (LinearLayout) findViewById(R.id.entire_screen); - buttonMenu = (ImageButton) findViewById(R.id.menu_buttons); - buttonMenu.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - PopupMenu popupMenu = new PopupMenu(FtcRobotControllerActivity.this, v); - popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() { - @Override - public boolean onMenuItemClick(MenuItem item) { - return onOptionsItemSelected(item); // Delegate to the handler for the hardware menu button - } - }); - popupMenu.inflate(R.menu.ftc_robot_controller); - AnnotatedHooksClassFilter.getInstance().callOnCreateMenuMethods( - FtcRobotControllerActivity.this, popupMenu.getMenu()); - popupMenu.show(); - } - }); - - updateMonitorLayout(getResources().getConfiguration()); - - BlocksOpMode.setActivityAndWebView(this, (WebView) findViewById(R.id.webViewBlocksRuntime)); - - ExternalLibraries.getInstance().onCreate(); - onBotJavaHelper = new OnBotJavaHelperImpl(); - - /* - * Paranoia as the ClassManagerFactory requires EXTERNAL_STORAGE permissions - * and we've seen on the DS where the finish() call above does not short-circuit - * the onCreate() call for the activity and then we crash here because we don't - * have permissions. So... - */ - if (permissionsValidated) { - ClassManager.getInstance().setOnBotJavaClassHelper(onBotJavaHelper); - ClassManagerFactory.registerFilters(); - ClassManagerFactory.processAllClasses(); - } - - cfgFileMgr = new RobotConfigFileManager(this); - - // Clean up 'dirty' status after a possible crash - RobotConfigFile configFile = cfgFileMgr.getActiveConfig(); - if (configFile.isDirty()) { - configFile.markClean(); - cfgFileMgr.setActiveConfig(false, configFile); - } - - textDeviceName = (TextView) findViewById(R.id.textDeviceName); - textNetworkConnectionStatus = (TextView) findViewById(R.id.textNetworkConnectionStatus); - textRobotStatus = (TextView) findViewById(R.id.textRobotStatus); - textOpMode = (TextView) findViewById(R.id.textOpMode); - textErrorMessage = (TextView) findViewById(R.id.textErrorMessage); - textGamepad[0] = (TextView) findViewById(R.id.textGamepad1); - textGamepad[1] = (TextView) findViewById(R.id.textGamepad2); - immersion = new ImmersiveMode(getWindow().getDecorView()); - dimmer = new Dimmer(this); - dimmer.longBright(); - - programmingModeManager = new ProgrammingModeManager(); - programmingModeManager.register(new ProgrammingWebHandlers()); - programmingModeManager.register(new OnBotJavaProgrammingMode()); - - updateUI = createUpdateUI(); - callback = createUICallback(updateUI); - - PreferenceManager.setDefaultValues(this, R.xml.app_settings, false); - - WifiManager wifiManager = (WifiManager) getApplicationContext().getSystemService(Context.WIFI_SERVICE); - wifiLock = wifiManager.createWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF, ""); - - hittingMenuButtonBrightensScreen(); - - wifiLock.acquire(); - callback.networkConnectionUpdate(NetworkConnection.NetworkEvent.DISCONNECTED); - readNetworkType(); - ServiceController.startService(FtcRobotControllerWatchdogService.class); - bindToService(); - RobotLog.logAppInfo(); - RobotLog.logDeviceInfo(); - AndroidBoard.getInstance().logAndroidBoardInfo(); - - if (preferencesHelper.readBoolean(getString(R.string.pref_wifi_automute), false)) { - initWifiMute(true); - } - - FtcAboutActivity.setBuildTimeFromBuildConfig(BuildConfig.APP_BUILD_TIME); - - // check to see if there is a preferred Wi-Fi to use. - checkPreferredChannel(); - - AnnotatedHooksClassFilter.getInstance().callOnCreateMethods(this); - } - - protected UpdateUI createUpdateUI() { - Restarter restarter = new RobotRestarter(); - UpdateUI result = new UpdateUI(this, dimmer); - result.setRestarter(restarter); - result.setTextViews(textNetworkConnectionStatus, textRobotStatus, textGamepad, textOpMode, textErrorMessage, textDeviceName); - return result; - } - - protected UpdateUI.Callback createUICallback(UpdateUI updateUI) { - UpdateUI.Callback result = updateUI.new Callback(); - result.setStateMonitor(new SoundPlayingRobotMonitor()); - return result; - } - - @Override - protected void onStart() { - super.onStart(); - RobotLog.vv(TAG, "onStart()"); - - entireScreenLayout.setOnTouchListener(new View.OnTouchListener() { - @Override - public boolean onTouch(View v, MotionEvent event) { - dimmer.handleDimTimer(); - return false; - } - }); - } - - @Override - protected void onResume() { - super.onResume(); - RobotLog.vv(TAG, "onResume()"); - - // In case the user just got back from fixing their clock, refresh ClockWarningSource - ClockWarningSource.getInstance().onPossibleRcClockUpdate(); - } - - @Override - protected void onPause() { - super.onPause(); - RobotLog.vv(TAG, "onPause()"); - } - - @Override - protected void onStop() { - // Note: this gets called even when the configuration editor is launched. That is, it gets - // called surprisingly often. So, we don't actually do much here. - super.onStop(); - RobotLog.vv(TAG, "onStop()"); - } - - @Override - protected void onDestroy() { - super.onDestroy(); - RobotLog.vv(TAG, "onDestroy()"); - - shutdownRobot(); // Ensure the robot is put away to bed - if (callback != null) callback.close(); - - PreferenceRemoterRC.getInstance().stop(prefRemoterStartResult); - DeviceNameManagerFactory.getInstance().stop(deviceNameStartResult); - - unbindFromService(); - // If the app manually (?) is stopped, then we don't need the auto-starting function (?) - ServiceController.stopService(FtcRobotControllerWatchdogService.class); - if (wifiLock != null) wifiLock.release(); - if (preferencesHelper != null) preferencesHelper.getSharedPreferences().unregisterOnSharedPreferenceChangeListener(sharedPreferencesListener); - - RobotLog.cancelWriteLogcatToDisk(); - - AnnotatedHooksClassFilter.getInstance().callOnDestroyMethods(this); - } - - protected void bindToService() { - readNetworkType(); - Intent intent = new Intent(this, FtcRobotControllerService.class); - intent.putExtra(NetworkConnectionFactory.NETWORK_CONNECTION_TYPE, networkType); - serviceShouldUnbind = bindService(intent, connection, Context.BIND_AUTO_CREATE); - } - - protected void unbindFromService() { - if (serviceShouldUnbind) { - unbindService(connection); - serviceShouldUnbind = false; - } - } - - protected void readNetworkType() { - // Control hubs are always running the access point model. Everything else, for the time - // being always runs the Wi-Fi Direct model. - if (Device.isRevControlHub() == true) { - networkType = NetworkType.RCWIRELESSAP; - } else { - networkType = NetworkType.fromString(preferencesHelper.readString(context.getString(R.string.pref_pairing_kind), NetworkType.globalDefaultAsString())); - } - - // update the app_settings - preferencesHelper.writeStringPrefIfDifferent(context.getString(R.string.pref_pairing_kind), networkType.toString()); - } - - @Override - public void onWindowFocusChanged(boolean hasFocus) { - super.onWindowFocusChanged(hasFocus); - - if (hasFocus) { - immersion.hideSystemUI(); - getWindow().setFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION, WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION); - } - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - getMenuInflater().inflate(R.menu.ftc_robot_controller, menu); - AnnotatedHooksClassFilter.getInstance().callOnCreateMenuMethods(this, menu); - return true; - } - - private boolean isRobotRunning() { - if (controllerService == null) { - return false; - } - - Robot robot = controllerService.getRobot(); - - if ((robot == null) || (robot.eventLoopManager == null)) { - return false; - } - - RobotState robotState = robot.eventLoopManager.state; - - if (robotState != RobotState.RUNNING) { - return false; - } else { - return true; - } - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - int id = item.getItemId(); - - if (id == R.id.action_program_and_manage) { - if (isRobotRunning()) { - Intent programmingModeIntent = new Intent(AppUtil.getDefContext(), ProgramAndManageActivity.class); - RobotControllerWebInfo webInfo = programmingModeManager.getWebServer().getConnectionInformation(); - programmingModeIntent.putExtra(LaunchActivityConstantsList.RC_WEB_INFO, webInfo.toJson()); - startActivity(programmingModeIntent); - } else { - AppUtil.getInstance().showToast(UILocation.ONLY_LOCAL, context.getString(R.string.toastWifiUpBeforeProgrammingMode)); - } - } else if (id == R.id.action_inspection_mode) { - Intent inspectionModeIntent = new Intent(AppUtil.getDefContext(), RcInspectionActivity.class); - startActivity(inspectionModeIntent); - return true; - } else if (id == R.id.action_restart_robot) { - dimmer.handleDimTimer(); - AppUtil.getInstance().showToast(UILocation.BOTH, context.getString(R.string.toastRestartingRobot)); - requestRobotRestart(); - return true; - } - else if (id == R.id.action_configure_robot) { - EditParameters parameters = new EditParameters(); - Intent intentConfigure = new Intent(AppUtil.getDefContext(), FtcLoadFileActivity.class); - parameters.putIntent(intentConfigure); - startActivityForResult(intentConfigure, RequestCode.CONFIGURE_ROBOT_CONTROLLER.ordinal()); - } - else if (id == R.id.action_settings) { - // historical: this once erroneously used FTC_CONFIGURE_REQUEST_CODE_ROBOT_CONTROLLER - Intent settingsIntent = new Intent(AppUtil.getDefContext(), FtcRobotControllerSettingsActivity.class); - startActivityForResult(settingsIntent, RequestCode.SETTINGS_ROBOT_CONTROLLER.ordinal()); - return true; - } - else if (id == R.id.action_about) { - Intent intent = new Intent(AppUtil.getDefContext(), FtcAboutActivity.class); - startActivity(intent); - return true; - } - else if (id == R.id.action_exit_app) { - - //Clear backstack and everything to prevent edge case where VM might be - //restarted (after it was exited) if more than one activity was on the - //backstack for some reason. - finishAffinity(); - - //For lollipop and up, we can clear ourselves from the recents list too - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) { - ActivityManager manager = (ActivityManager) getSystemService(ACTIVITY_SERVICE); - List tasks = manager.getAppTasks(); - - for (ActivityManager.AppTask task : tasks) { - task.finishAndRemoveTask(); - } - } - - // Allow the user to use the Control Hub operating system's UI, instead of relaunching the app - AppAliveNotifier.getInstance().disableAppWatchdogUntilNextAppStart(); - - //Finally, nuke the VM from orbit - AppUtil.getInstance().exitApplication(); - - return true; - } - - return super.onOptionsItemSelected(item); - } - - @Override - public void onConfigurationChanged(Configuration newConfig) { - super.onConfigurationChanged(newConfig); - // don't destroy assets on screen rotation - updateMonitorLayout(newConfig); - } - - /** - * Updates the orientation of monitorContainer (which contains cameraMonitorView) - * based on the given configuration. Makes the children split the space. - */ - private void updateMonitorLayout(Configuration configuration) { - LinearLayout monitorContainer = (LinearLayout) findViewById(R.id.monitorContainer); - if (configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) { - // When the phone is landscape, lay out the monitor views horizontally. - monitorContainer.setOrientation(LinearLayout.HORIZONTAL); - for (int i = 0; i < monitorContainer.getChildCount(); i++) { - View view = monitorContainer.getChildAt(i); - view.setLayoutParams(new LayoutParams(0, LayoutParams.MATCH_PARENT, 1 /* weight */)); - } - } else { - // When the phone is portrait, lay out the monitor views vertically. - monitorContainer.setOrientation(LinearLayout.VERTICAL); - for (int i = 0; i < monitorContainer.getChildCount(); i++) { - View view = monitorContainer.getChildAt(i); - view.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, 0, 1 /* weight */)); - } - } - monitorContainer.requestLayout(); - } - - @Override - protected void onActivityResult(int request, int result, Intent intent) { - if (request == REQUEST_CONFIG_WIFI_CHANNEL) { - if (result == RESULT_OK) { - AppUtil.getInstance().showToast(UILocation.BOTH, context.getString(R.string.toastWifiConfigurationComplete)); - } - } - // was some historical confusion about launch codes here, so we err safely - if (request == RequestCode.CONFIGURE_ROBOT_CONTROLLER.ordinal() || request == RequestCode.SETTINGS_ROBOT_CONTROLLER.ordinal()) { - // We always do a refresh, whether it was a cancel or an OK, for robustness - shutdownRobot(); - cfgFileMgr.getActiveConfigAndUpdateUI(); - updateUIAndRequestRobotSetup(); - } - } - - public void onServiceBind(final FtcRobotControllerService service) { - RobotLog.vv(FtcRobotControllerService.TAG, "%s.controllerService=bound", TAG); - controllerService = service; - updateUI.setControllerService(controllerService); - - controllerService.setOnBotJavaHelper(onBotJavaHelper); - - updateUIAndRequestRobotSetup(); - programmingModeManager.setState(new FtcRobotControllerServiceState() { - @NonNull - @Override - public WebServer getWebServer() { - return service.getWebServer(); - } - - @Nullable - @Override - public OnBotJavaHelper getOnBotJavaHelper() { - return service.getOnBotJavaHelper(); - } - - @Override - public EventLoopManager getEventLoopManager() { - return service.getRobot().eventLoopManager; - } - }); - - AnnotatedHooksClassFilter.getInstance().callWebHandlerRegistrarMethods(this, - service.getWebServer().getWebHandlerManager()); - } - - private void updateUIAndRequestRobotSetup() { - if (controllerService != null) { - callback.networkConnectionUpdate(controllerService.getNetworkConnectionStatus()); - callback.updateRobotStatus(controllerService.getRobotStatus()); - // Only show this first-time toast on headless systems: what we have now on non-headless suffices - requestRobotSetup(LynxConstants.isRevControlHub() - ? new Runnable() { - @Override public void run() { - showRestartRobotCompleteToast(R.string.toastRobotSetupComplete); - } - } - : null); - } - } - - private void requestRobotSetup(@Nullable Runnable runOnComplete) { - if (controllerService == null) return; - - RobotConfigFile file = cfgFileMgr.getActiveConfigAndUpdateUI(); - HardwareFactory hardwareFactory = new HardwareFactory(context); - try { - hardwareFactory.setXmlPullParser(file.getXml()); - } catch (FileNotFoundException | XmlPullParserException e) { - RobotLog.ww(TAG, e, "Unable to set configuration file %s. Falling back on noConfig.", file.getName()); - file = RobotConfigFile.noConfig(cfgFileMgr); - try { - hardwareFactory.setXmlPullParser(file.getXml()); - cfgFileMgr.setActiveConfigAndUpdateUI(false, file); - } catch (FileNotFoundException | XmlPullParserException e1) { - RobotLog.ee(TAG, e1, "Failed to fall back on noConfig"); - } - } - - OpModeRegister userOpModeRegister = createOpModeRegister(); - eventLoop = new FtcEventLoop(hardwareFactory, userOpModeRegister, callback, this); - FtcEventLoopIdle idleLoop = new FtcEventLoopIdle(hardwareFactory, userOpModeRegister, callback, this); - - controllerService.setCallback(callback); - controllerService.setupRobot(eventLoop, idleLoop, runOnComplete); - - passReceivedUsbAttachmentsToEventLoop(); - AndroidBoard.showErrorIfUnknownControlHub(); - - AnnotatedHooksClassFilter.getInstance().callOnCreateEventLoopMethods(this, eventLoop); - } - - protected OpModeRegister createOpModeRegister() { - return new FtcOpModeRegister(); - } - - private void shutdownRobot() { - if (controllerService != null) controllerService.shutdownRobot(); - } - - private void requestRobotRestart() { - AppUtil.getInstance().showToast(UILocation.BOTH, AppUtil.getDefContext().getString(R.string.toastRestartingRobot)); - // - RobotLog.clearGlobalErrorMsg(); - RobotLog.clearGlobalWarningMsg(); - shutdownRobot(); - requestRobotSetup(new Runnable() { - @Override public void run() { - showRestartRobotCompleteToast(R.string.toastRestartRobotComplete); - } - }); - } - - private void showRestartRobotCompleteToast(@StringRes int resid) { - AppUtil.getInstance().showToast(UILocation.BOTH, AppUtil.getDefContext().getString(resid)); - } - - private void checkPreferredChannel() { - // For P2P network, check to see what preferred channel is. - if (networkType == NetworkType.WIFIDIRECT) { - int prefChannel = preferencesHelper.readInt(getString(com.qualcomm.ftccommon.R.string.pref_wifip2p_channel), -1); - if (prefChannel == -1) { - prefChannel = 0; - RobotLog.vv(TAG, "pref_wifip2p_channel: No preferred channel defined. Will use a default value of %d", prefChannel); - } else { - RobotLog.vv(TAG, "pref_wifip2p_channel: Found existing preferred channel (%d).", prefChannel); - } - - // attempt to set the preferred channel. - RobotLog.vv(TAG, "pref_wifip2p_channel: attempting to set preferred channel..."); - wifiDirectChannelChanger = new WifiDirectChannelChanger(); - wifiDirectChannelChanger.changeToChannel(prefChannel); - } - } - - protected void hittingMenuButtonBrightensScreen() { - ActionBar actionBar = getActionBar(); - if (actionBar != null) { - actionBar.addOnMenuVisibilityListener(new ActionBar.OnMenuVisibilityListener() { - @Override - public void onMenuVisibilityChanged(boolean isVisible) { - if (isVisible) { - dimmer.handleDimTimer(); - } - } - }); - } - } - - protected class SharedPreferencesListener implements SharedPreferences.OnSharedPreferenceChangeListener { - @Override public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { - if (key.equals(context.getString(R.string.pref_app_theme))) { - ThemedActivity.restartForAppThemeChange(getTag(), getString(R.string.appThemeChangeRestartNotifyRC)); - } else if (key.equals(context.getString(R.string.pref_wifi_automute))) { - if (preferencesHelper.readBoolean(context.getString(R.string.pref_wifi_automute), false)) { - initWifiMute(true); - } else { - initWifiMute(false); - } - } - } - } - - protected void initWifiMute(boolean enable) { - if (enable) { - wifiMuteStateMachine = new WifiMuteStateMachine(); - wifiMuteStateMachine.initialize(); - wifiMuteStateMachine.start(); - - motionDetection = new MotionDetection(2.0, 10); - motionDetection.startListening(); - motionDetection.registerListener(new MotionDetection.MotionDetectionListener() { - @Override - public void onMotionDetected(double vector) - { - wifiMuteStateMachine.consumeEvent(WifiMuteEvent.USER_ACTIVITY); - } - }); - } else { - wifiMuteStateMachine.stop(); - wifiMuteStateMachine = null; - motionDetection.stopListening(); - motionDetection.purgeListeners(); - motionDetection = null; - } - } - - @Override - public void onUserInteraction() { - if (wifiMuteStateMachine != null) { - wifiMuteStateMachine.consumeEvent(WifiMuteEvent.USER_ACTIVITY); - } - } -} diff --git a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/internal/PermissionValidatorWrapper.java b/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/internal/PermissionValidatorWrapper.java deleted file mode 100644 index a0094bc..0000000 --- a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/internal/PermissionValidatorWrapper.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright (c) 2018 Craig MacFarlane - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted - * (subject to the limitations in the disclaimer below) provided that the following conditions are - * met: - * - * Redistributions of source code must retain the above copyright notice, this list of conditions - * and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this list of conditions - * and the following disclaimer in the documentation and/or other materials provided with the - * distribution. - * - * Neither the name of Craig MacFarlane nor the names of its contributors may be used to - * endorse or promote products derived from this software without specific prior written permission. - * - * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS LICENSE. THIS - * SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS - * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, - * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF - * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package org.firstinspires.ftc.robotcontroller.internal; - -import android.Manifest; -import android.os.Bundle; - -import com.qualcomm.ftcrobotcontroller.R; - -import org.firstinspires.ftc.robotcore.internal.system.Misc; -import org.firstinspires.ftc.robotcore.internal.system.PermissionValidatorActivity; - -import java.util.ArrayList; -import java.util.List; - -public class PermissionValidatorWrapper extends PermissionValidatorActivity { - - private final String TAG = "PermissionValidatorWrapper"; - - /* - * The list of dangerous permissions the robot controller needs. - */ - protected List robotControllerPermissions = new ArrayList() {{ - add(Manifest.permission.WRITE_EXTERNAL_STORAGE); - add(Manifest.permission.READ_EXTERNAL_STORAGE); - add(Manifest.permission.CAMERA); - add(Manifest.permission.ACCESS_COARSE_LOCATION); - add(Manifest.permission.ACCESS_FINE_LOCATION); - add(Manifest.permission.READ_PHONE_STATE); - }}; - - private final static Class startApplication = FtcRobotControllerActivity.class; - - public String mapPermissionToExplanation(final String permission) { - if (permission.equals(Manifest.permission.WRITE_EXTERNAL_STORAGE)) { - return Misc.formatForUser(R.string.permRcWriteExternalStorageExplain); - } else if (permission.equals(Manifest.permission.READ_EXTERNAL_STORAGE)) { - return Misc.formatForUser(R.string.permRcReadExternalStorageExplain); - } else if (permission.equals(Manifest.permission.CAMERA)) { - return Misc.formatForUser(R.string.permRcCameraExplain); - } else if (permission.equals(Manifest.permission.ACCESS_COARSE_LOCATION)) { - return Misc.formatForUser(R.string.permAccessLocationExplain); - } else if (permission.equals(Manifest.permission.ACCESS_FINE_LOCATION)) { - return Misc.formatForUser(R.string.permAccessLocationExplain); - } else if (permission.equals(Manifest.permission.READ_PHONE_STATE)) { - return Misc.formatForUser(R.string.permReadPhoneState); - } - return Misc.formatForUser(R.string.permGenericExplain); - } - - @Override - protected void onCreate(Bundle savedInstanceState) - { - super.onCreate(savedInstanceState); - - permissions = robotControllerPermissions; - } - - protected Class onStartApplication() - { - FtcRobotControllerActivity.setPermissionsValidated(); - return startApplication; - } -} diff --git a/library/FtcRobotController/src/main/res/drawable-xhdpi/icon_menu.png b/library/FtcRobotController/src/main/res/drawable-xhdpi/icon_menu.png deleted file mode 100644 index 6b9e997c5624fad049a7192d54034336f5216eaa..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 975 zcmaJ=&ui0A91l2#lnrl!!r+mCOj+|@vb9~p;^^|4t!PT=I_)m9G#pWdU$RplDe*&&7KHhQB($9^!a$R3dVwx}KYA+y&(+2IISW~#L0aB&Cn77IO5N#f zIjSoz+y?WB#tD4FY>@cdL98XZ*yYvuIlYD==(?~iT|7^!VO4=aBLZ?#KIkKb4NO_K;|1yE%`VEav~mzLJ8(!D>muioJkQlktRkF8ra7k=vRs-iE*7!L?Zr|q zUo=#?kHys4@kzk?Sa*Px(NJtE2tVYJF^RlK#5E)8gKpuPH#`?Sl&^<%hvtGxL$Q!2 z1Jm*THSYcu*HC&Kh?g4!`ICTaKH}nj-#mN6ABJAW#d>n~?W3F#z}Uz!{`Gr5_jnqA zUORkw1MD9>KQZ@4-u-yB1}Daz{yaHcMcW7Kr}vK0-lwbUg8E?e#p==5Tj>)kmn!)6 H;`)<6M=di_ diff --git a/library/FtcRobotController/src/main/res/drawable-xhdpi/icon_robotcontroller.png b/library/FtcRobotController/src/main/res/drawable-xhdpi/icon_robotcontroller.png deleted file mode 100644 index 022552f409c06b5c93aec2300324913fadcaac51..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4777 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 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/library/FtcRobotController/src/main/res/menu/ftc_robot_controller.xml b/library/FtcRobotController/src/main/res/menu/ftc_robot_controller.xml deleted file mode 100644 index 657c1aa..0000000 --- a/library/FtcRobotController/src/main/res/menu/ftc_robot_controller.xml +++ /dev/null @@ -1,78 +0,0 @@ - - -

- - - - - - - - - - - - - - - diff --git a/library/FtcRobotController/src/main/res/raw/gold.wav b/library/FtcRobotController/src/main/res/raw/gold.wav deleted file mode 100644 index 3a7baf88159a373d490064e774d8796e02bcf0bd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 104016 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ɍ)X7ft-z(-0Z_L+ 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 Vv0|mMjxCTc%YVx6WRILE{~xn&YM%fA diff --git a/library/FtcRobotController/src/main/res/raw/silver.wav b/library/FtcRobotController/src/main/res/raw/silver.wav deleted file mode 100644 index 25918a72ae51c412a9a6268279b449fd19639272..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 99068 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 B7J>i( diff --git a/library/FtcRobotController/src/main/res/values/dimens.xml b/library/FtcRobotController/src/main/res/values/dimens.xml deleted file mode 100644 index 63f1bab..0000000 --- a/library/FtcRobotController/src/main/res/values/dimens.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - - 16dp - 5dp - - \ No newline at end of file diff --git a/library/FtcRobotController/src/main/res/values/strings.xml b/library/FtcRobotController/src/main/res/values/strings.xml deleted file mode 100644 index 6ea191e..0000000 --- a/library/FtcRobotController/src/main/res/values/strings.xml +++ /dev/null @@ -1,72 +0,0 @@ - - - - - - - FTC Robot Controller - - - Self Inspect - Program & Manage - Blocks - Settings - Restart Robot - Configure Robot - About - Exit - - - Configuration Complete - Restarting Robot - The Robot Controller must be fully up and running before entering Program and Manage Mode. - - - - @style/AppThemeRedRC - @style/AppThemeGreenRC - @style/AppThemeBlueRC - @style/AppThemePurpleRC - @style/AppThemeOrangeRC - @style/AppThemeTealRC - - - pref_ftc_season_year_of_current_rc_new - - @string/packageNameRobotController - - diff --git a/library/FtcRobotController/src/main/res/values/styles.xml b/library/FtcRobotController/src/main/res/values/styles.xml deleted file mode 100644 index 07689c0..0000000 --- a/library/FtcRobotController/src/main/res/values/styles.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/library/FtcRobotController/src/main/res/xml/app_settings.xml b/library/FtcRobotController/src/main/res/xml/app_settings.xml deleted file mode 100644 index 58d3aa9..0000000 --- a/library/FtcRobotController/src/main/res/xml/app_settings.xml +++ /dev/null @@ -1,93 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/library/FtcRobotController/src/main/res/xml/device_filter.xml b/library/FtcRobotController/src/main/res/xml/device_filter.xml deleted file mode 100644 index 7b75350..0000000 --- a/library/FtcRobotController/src/main/res/xml/device_filter.xml +++ /dev/null @@ -1,49 +0,0 @@ - - - - - - - - - - - - - - diff --git a/library/FullPanels/build.gradle.kts b/library/FullPanels/build.gradle.kts index 97ca274..5096e2c 100644 --- a/library/FullPanels/build.gradle.kts +++ b/library/FullPanels/build.gradle.kts @@ -2,14 +2,15 @@ val pluginNamespace = "com.bylazar.fullpanels" val pluginVersion = "1.0.12" plugins { - id("com.android.library") - id("org.jetbrains.kotlin.android") + id("dev.frozenmilk.android-library") version "11.1.0-1.1.1" id("com.bylazar.svelte-assets") id("dev.frozenmilk.publish") version "0.0.5" id("dev.frozenmilk.doc") version "0.0.5" id("dev.frozenmilk.build-meta-data") version "0.0.2" } +android.namespace = pluginNamespace + svelteAssets { assetsPath = assetPathForPlugin(pluginNamespace) } @@ -30,48 +31,16 @@ meta { registerField("version", "String") { "\"$version\"" } } -android { - namespace = pluginNamespace - - defaultConfig { - compileSdk = 34 - minSdk = 24 - } - - buildTypes { - getByName("release") { - isMinifyEnabled = false - } - } - - compileOptions { - sourceCompatibility = JavaVersion.VERSION_11 - targetCompatibility = JavaVersion.VERSION_11 - } - - kotlinOptions { - jvmTarget = "11" - } - - publishing { - singleVariant("release") {} +ftc { + kotlin() + sdk { + compileOnly(RobotCore) + compileOnly(FtcCommon) + compileOnly(RobotServer) } } -repositories { - maven("https://repo.dairy.foundation/releases") -} - dependencies { - compileOnly("org.firstinspires.ftc:Inspection:11.0.0") - compileOnly("org.firstinspires.ftc:Blocks:11.0.0") - compileOnly("org.firstinspires.ftc:RobotCore:11.0.0") - compileOnly("org.firstinspires.ftc:RobotServer:11.0.0") - compileOnly("org.firstinspires.ftc:OnBotJava:11.0.0") - compileOnly("org.firstinspires.ftc:Hardware:11.0.0") - compileOnly("org.firstinspires.ftc:FtcCommon:11.0.0") - compileOnly("org.firstinspires.ftc:Vision:11.0.0") - api(project(":Panels")) api(project(":OpModeControl")) diff --git a/library/FullPanels/web/bun.lock b/library/FullPanels/web/bun.lock index b8db93a..0d865e4 100644 --- a/library/FullPanels/web/bun.lock +++ b/library/FullPanels/web/bun.lock @@ -1,5 +1,6 @@ { "lockfileVersion": 1, + "configVersion": 0, "workspaces": { "": { "name": "panels-plugin", diff --git a/library/Gamepad/build.gradle.kts b/library/Gamepad/build.gradle.kts index a89a37a..95b22da 100644 --- a/library/Gamepad/build.gradle.kts +++ b/library/Gamepad/build.gradle.kts @@ -2,14 +2,15 @@ val pluginNamespace = "com.bylazar.gamepad" val pluginVersion = "1.0.4" plugins { - id("com.android.library") - id("org.jetbrains.kotlin.android") + id("dev.frozenmilk.android-library") version "11.1.0-1.1.1" id("com.bylazar.svelte-assets") id("dev.frozenmilk.publish") version "0.0.5" id("dev.frozenmilk.doc") version "0.0.5" id("dev.frozenmilk.build-meta-data") version "0.0.2" } +android.namespace = pluginNamespace + svelteAssets { assetsPath = assetPathForPlugin(pluginNamespace) } @@ -30,44 +31,15 @@ meta { registerField("version", "String") { "\"$version\"" } } -android { - namespace = pluginNamespace - - defaultConfig { - compileSdk = 34 - minSdk = 24 - } - - buildTypes { - getByName("release") { - isMinifyEnabled = false - } - } - - compileOptions { - sourceCompatibility = JavaVersion.VERSION_11 - targetCompatibility = JavaVersion.VERSION_11 - } - - kotlinOptions { - jvmTarget = "11" - } - - publishing { - singleVariant("release") {} +ftc { + kotlin() + sdk { + compileOnly(RobotCore) + compileOnly(FtcCommon) } } dependencies { - compileOnly("org.firstinspires.ftc:Inspection:11.0.0") - compileOnly("org.firstinspires.ftc:Blocks:11.0.0") - compileOnly("org.firstinspires.ftc:RobotCore:11.0.0") - compileOnly("org.firstinspires.ftc:RobotServer:11.0.0") - compileOnly("org.firstinspires.ftc:OnBotJava:11.0.0") - compileOnly("org.firstinspires.ftc:Hardware:11.0.0") - compileOnly("org.firstinspires.ftc:FtcCommon:11.0.0") - compileOnly("org.firstinspires.ftc:Vision:11.0.0") - compileOnly(project(":Panels")) } diff --git a/library/Gamepad/web/bun.lock b/library/Gamepad/web/bun.lock index b8db93a..0d865e4 100644 --- a/library/Gamepad/web/bun.lock +++ b/library/Gamepad/web/bun.lock @@ -1,5 +1,6 @@ { "lockfileVersion": 1, + "configVersion": 0, "workspaces": { "": { "name": "panels-plugin", diff --git a/library/Graph/build.gradle.kts b/library/Graph/build.gradle.kts index 0dcd2ec..86e6554 100644 --- a/library/Graph/build.gradle.kts +++ b/library/Graph/build.gradle.kts @@ -2,14 +2,15 @@ val pluginNamespace = "com.bylazar.graph" val pluginVersion = "1.0.4" plugins { - id("com.android.library") - id("org.jetbrains.kotlin.android") + id("dev.frozenmilk.android-library") version "11.1.0-1.1.1" id("com.bylazar.svelte-assets") id("dev.frozenmilk.publish") version "0.0.5" id("dev.frozenmilk.doc") version "0.0.5" id("dev.frozenmilk.build-meta-data") version "0.0.2" } +android.namespace = pluginNamespace + svelteAssets { assetsPath = assetPathForPlugin(pluginNamespace) } @@ -30,31 +31,12 @@ meta { registerField("version", "String") { "\"$version\"" } } -android { - namespace = pluginNamespace - - defaultConfig { - compileSdk = 34 - minSdk = 24 - } - - buildTypes { - getByName("release") { - isMinifyEnabled = false - } - } - - compileOptions { - sourceCompatibility = JavaVersion.VERSION_11 - targetCompatibility = JavaVersion.VERSION_11 - } - - kotlinOptions { - jvmTarget = "11" - } - - publishing { - singleVariant("release") {} +ftc { + kotlin() + sdk { + compileOnly(RobotCore) + compileOnly(FtcCommon) + compileOnly(RobotServer) } } diff --git a/library/Graph/web/bun.lock b/library/Graph/web/bun.lock index bfc69e3..0832e7d 100644 --- a/library/Graph/web/bun.lock +++ b/library/Graph/web/bun.lock @@ -1,5 +1,6 @@ { "lockfileVersion": 1, + "configVersion": 0, "workspaces": { "": { "name": "panels-plugin", diff --git a/library/Lights/build.gradle.kts b/library/Lights/build.gradle.kts index 2a53a34..d0bc98f 100644 --- a/library/Lights/build.gradle.kts +++ b/library/Lights/build.gradle.kts @@ -2,14 +2,15 @@ val pluginNamespace = "com.bylazar.lights" val pluginVersion = "1.0.3" plugins { - id("com.android.library") - id("org.jetbrains.kotlin.android") + id("dev.frozenmilk.android-library") version "11.1.0-1.1.1" id("com.bylazar.svelte-assets") id("dev.frozenmilk.publish") version "0.0.5" id("dev.frozenmilk.doc") version "0.0.5" id("dev.frozenmilk.build-meta-data") version "0.0.2" } +android.namespace = pluginNamespace + svelteAssets { assetsPath = assetPathForPlugin(pluginNamespace) } @@ -30,44 +31,16 @@ meta { registerField("version", "String") { "\"$version\"" } } -android { - namespace = pluginNamespace - - defaultConfig { - compileSdk = 34 - minSdk = 24 - } - - buildTypes { - getByName("release") { - isMinifyEnabled = false - } - } - - compileOptions { - sourceCompatibility = JavaVersion.VERSION_11 - targetCompatibility = JavaVersion.VERSION_11 - } - - kotlinOptions { - jvmTarget = "11" - } - - publishing { - singleVariant("release") {} +ftc { + kotlin() + sdk { + compileOnly(RobotCore) + compileOnly(FtcCommon) + compileOnly(RobotServer) } } dependencies { - compileOnly("org.firstinspires.ftc:Inspection:11.0.0") - compileOnly("org.firstinspires.ftc:Blocks:11.0.0") - compileOnly("org.firstinspires.ftc:RobotCore:11.0.0") - compileOnly("org.firstinspires.ftc:RobotServer:11.0.0") - compileOnly("org.firstinspires.ftc:OnBotJava:11.0.0") - compileOnly("org.firstinspires.ftc:Hardware:11.0.0") - compileOnly("org.firstinspires.ftc:FtcCommon:11.0.0") - compileOnly("org.firstinspires.ftc:Vision:11.0.0") - compileOnly(project(":Panels")) } diff --git a/library/Lights/web/bun.lock b/library/Lights/web/bun.lock index b8db93a..0d865e4 100644 --- a/library/Lights/web/bun.lock +++ b/library/Lights/web/bun.lock @@ -1,5 +1,6 @@ { "lockfileVersion": 1, + "configVersion": 0, "workspaces": { "": { "name": "panels-plugin", diff --git a/library/LimelightProxy/build.gradle.kts b/library/LimelightProxy/build.gradle.kts index a59e899..c6a87df 100644 --- a/library/LimelightProxy/build.gradle.kts +++ b/library/LimelightProxy/build.gradle.kts @@ -2,14 +2,15 @@ val pluginNamespace = "com.bylazar.limelightproxy" val pluginVersion = "1.0.4" plugins { - id("com.android.library") - id("org.jetbrains.kotlin.android") + id("dev.frozenmilk.android-library") version "11.1.0-1.1.1" id("com.bylazar.svelte-assets") id("dev.frozenmilk.publish") version "0.0.5" id("dev.frozenmilk.doc") version "0.0.5" id("dev.frozenmilk.build-meta-data") version "0.0.2" } +android.namespace = pluginNamespace + svelteAssets { assetsPath = assetPathForPlugin(pluginNamespace) } @@ -30,48 +31,19 @@ meta { registerField("version", "String") { "\"${dairyPublishing.version}\"" } } -android { - namespace = pluginNamespace - - defaultConfig { - compileSdk = 34 - minSdk = 24 - } - - buildTypes { - getByName("release") { - isMinifyEnabled = false - } - } - - compileOptions { - sourceCompatibility = JavaVersion.VERSION_11 - targetCompatibility = JavaVersion.VERSION_11 - } - - kotlinOptions { - jvmTarget = "11" - } - - publishing { - singleVariant("release") {} +ftc { + kotlin() + sdk { + compileOnly(RobotCore) + compileOnly(FtcCommon) + compileOnly(RobotServer) } } dependencies { - compileOnly("org.firstinspires.ftc:Inspection:11.0.0") - compileOnly("org.firstinspires.ftc:Blocks:11.0.0") - compileOnly("org.firstinspires.ftc:RobotCore:11.0.0") - compileOnly("org.firstinspires.ftc:RobotServer:11.0.0") - compileOnly("org.firstinspires.ftc:OnBotJava:11.0.0") - compileOnly("org.firstinspires.ftc:Hardware:11.0.0") - compileOnly("org.firstinspires.ftc:FtcCommon:11.0.0") - compileOnly("org.firstinspires.ftc:Vision:11.0.0") - compileOnly(project(":Panels")) implementation("com.squareup.okhttp3:okhttp:4.10.0") - } afterEvaluate { diff --git a/library/LimelightProxy/web/bun.lock b/library/LimelightProxy/web/bun.lock index b8db93a..0d865e4 100644 --- a/library/LimelightProxy/web/bun.lock +++ b/library/LimelightProxy/web/bun.lock @@ -1,5 +1,6 @@ { "lockfileVersion": 1, + "configVersion": 0, "workspaces": { "": { "name": "panels-plugin", diff --git a/library/OpModeControl/build.gradle.kts b/library/OpModeControl/build.gradle.kts index b112a13..4febced 100644 --- a/library/OpModeControl/build.gradle.kts +++ b/library/OpModeControl/build.gradle.kts @@ -2,14 +2,15 @@ val pluginNamespace = "com.bylazar.opmodecontrol" val pluginVersion = "1.0.3" plugins { - id("com.android.library") - id("org.jetbrains.kotlin.android") + id("dev.frozenmilk.android-library") version "11.1.0-1.1.1" id("com.bylazar.svelte-assets") id("dev.frozenmilk.publish") version "0.0.5" id("dev.frozenmilk.doc") version "0.0.5" id("dev.frozenmilk.build-meta-data") version "0.0.2" } +android.namespace = pluginNamespace + svelteAssets { assetsPath = assetPathForPlugin(pluginNamespace) } @@ -30,50 +31,19 @@ meta { registerField("version", "String") { "\"${dairyPublishing.version}\"" } } -android { - namespace = pluginNamespace - - defaultConfig { - compileSdk = 34 - minSdk = 24 - } - - buildTypes { - getByName("release") { - isMinifyEnabled = false - } - } - - compileOptions { - sourceCompatibility = JavaVersion.VERSION_11 - targetCompatibility = JavaVersion.VERSION_11 +ftc { + kotlin() + sdk { + compileOnly(RobotCore) + compileOnly(FtcCommon) + compileOnly(RobotServer) } - - kotlinOptions { - jvmTarget = "11" - } - - publishing { - singleVariant("release") {} + dairy { + implementation(Sloth) } } -repositories { - maven("https://repo.dairy.foundation/releases") -} - dependencies { - compileOnly("org.firstinspires.ftc:Inspection:11.0.0") - compileOnly("org.firstinspires.ftc:Blocks:11.0.0") - compileOnly("org.firstinspires.ftc:RobotCore:11.0.0") - compileOnly("org.firstinspires.ftc:RobotServer:11.0.0") - compileOnly("org.firstinspires.ftc:OnBotJava:11.0.0") - compileOnly("org.firstinspires.ftc:Hardware:11.0.0") - compileOnly("org.firstinspires.ftc:FtcCommon:11.0.0") - compileOnly("org.firstinspires.ftc:Vision:11.0.0") - - implementation("dev.frozenmilk.sinister:Sloth:0.2.4") - compileOnly(project(":Panels")) } diff --git a/library/OpModeControl/web/bun.lock b/library/OpModeControl/web/bun.lock index b8db93a..0d865e4 100644 --- a/library/OpModeControl/web/bun.lock +++ b/library/OpModeControl/web/bun.lock @@ -1,5 +1,6 @@ { "lockfileVersion": 1, + "configVersion": 0, "workspaces": { "": { "name": "panels-plugin", diff --git a/library/Panels/build.gradle.kts b/library/Panels/build.gradle.kts index 70782a0..38bf630 100644 --- a/library/Panels/build.gradle.kts +++ b/library/Panels/build.gradle.kts @@ -2,14 +2,15 @@ val pluginNamespace = "com.bylazar.panels" val pluginVersion = "1.0.5" plugins { - id("com.android.library") - id("org.jetbrains.kotlin.android") + id("dev.frozenmilk.android-library") version "11.1.0-1.1.1" id("com.bylazar.svelte-assets") id("dev.frozenmilk.publish") version "0.0.5" id("dev.frozenmilk.doc") version "0.0.5" id("dev.frozenmilk.build-meta-data") version "0.0.2" } +android.namespace = pluginNamespace + svelteAssets { assetsPath = "web" buildDirPath = "build" @@ -31,63 +32,28 @@ meta { registerField("version", "String") { "\"$version\"" } } -android { - namespace = "com.bylazar.panels" - - defaultConfig { - compileSdk = 34 - minSdk = 24 - - testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" - consumerProguardFiles("consumer-rules.pro") - } - - buildTypes { - getByName("release") { - isMinifyEnabled = false - proguardFiles( - getDefaultProguardFile("proguard-android-optimize.txt"), - "proguard-rules.pro" - ) - } - } - - compileOptions { - sourceCompatibility = JavaVersion.VERSION_11 - targetCompatibility = JavaVersion.VERSION_11 - } - - kotlinOptions { - jvmTarget = "11" - } - - publishing { - singleVariant("release") {} +ftc { + kotlin() + sdk { + compileOnly(RobotCore) + compileOnly(FtcCommon) + compileOnly(RobotServer) + implementation(appcompat) } } dependencies { - implementation("androidx.core:core-ktx:1.3.0") - implementation("androidx.appcompat:appcompat:1.2.0") + implementation("androidx.core:core-ktx:1.2.0") testImplementation("junit:junit:4.13.2") - androidTestImplementation("androidx.test.ext:junit:1.2.1") - androidTestImplementation("androidx.test.espresso:espresso-core:3.6.1") - - compileOnly("org.firstinspires.ftc:Inspection:11.0.0") - compileOnly("org.firstinspires.ftc:Blocks:11.0.0") - compileOnly("org.firstinspires.ftc:RobotCore:11.0.0") - compileOnly("org.firstinspires.ftc:RobotServer:11.0.0") - compileOnly("org.firstinspires.ftc:OnBotJava:11.0.0") - compileOnly("org.firstinspires.ftc:Hardware:11.0.0") - compileOnly("org.firstinspires.ftc:FtcCommon:11.0.0") - compileOnly("org.firstinspires.ftc:Vision:11.0.0") + androidTestImplementation("androidx.test.ext:junit:1.1.5") + //androidTestImplementation("androidx.test.espresso:espresso-core:3.6.1") implementation("org.nanohttpd:nanohttpd-websocket:2.3.1") { exclude(module = "nanohttpd") } - implementation("org.jetbrains.kotlin:kotlin-reflect:1.9.23") + implementation("org.jetbrains.kotlin:kotlin-reflect:2.3.0") implementation("org.tukaani:xz:1.9") } diff --git a/library/Panels/src/androidTest/java/com/bylazar/panels/TestCompression.kt b/library/Panels/src/androidTest/java/com/bylazar/panels/TestCompression.kt index 4a20eb2..9fd2a6f 100644 --- a/library/Panels/src/androidTest/java/com/bylazar/panels/TestCompression.kt +++ b/library/Panels/src/androidTest/java/com/bylazar/panels/TestCompression.kt @@ -62,9 +62,8 @@ class TestCompression { return output.toByteArray() } - private fun decompressGzip(input: ByteArray, originalSize: Int): ByteArray { - val inputStream = ByteArrayInputStream(input) - return GZIPInputStream(inputStream).readAllBytes() + private fun decompressGzip(input: ByteArray, originalSize: Int) = ByteArrayInputStream(input).use { + GZIPInputStream(it).readBytes() } fun compressLzma(input: ByteArray): ByteArray { @@ -74,9 +73,8 @@ class TestCompression { return baos.toByteArray() } - fun decompressLzma(input: ByteArray): ByteArray { - val bais = ByteArrayInputStream(input) - return LZMAInputStream(bais).readAllBytes() + fun decompressLzma(input: ByteArray) = ByteArrayInputStream(input).use { + LZMAInputStream(it).readBytes() } fun compressDeflate(input: ByteArray): ByteArray { diff --git a/library/Panels/web/bun.lock b/library/Panels/web/bun.lock index 21500d6..806acf2 100644 --- a/library/Panels/web/bun.lock +++ b/library/Panels/web/bun.lock @@ -1,5 +1,6 @@ { "lockfileVersion": 1, + "configVersion": 0, "workspaces": { "": { "name": "panels-app", diff --git a/library/Pinger/build.gradle.kts b/library/Pinger/build.gradle.kts index 133085c..b0e3d49 100644 --- a/library/Pinger/build.gradle.kts +++ b/library/Pinger/build.gradle.kts @@ -2,14 +2,15 @@ val pluginNamespace = "com.bylazar.pinger" val pluginVersion = "1.0.3" plugins { - id("com.android.library") - id("org.jetbrains.kotlin.android") + id("dev.frozenmilk.android-library") version "11.1.0-1.1.1" id("com.bylazar.svelte-assets") id("dev.frozenmilk.publish") version "0.0.5" id("dev.frozenmilk.doc") version "0.0.5" id("dev.frozenmilk.build-meta-data") version "0.0.2" } +android.namespace = pluginNamespace + svelteAssets { assetsPath = assetPathForPlugin(pluginNamespace) } @@ -30,44 +31,16 @@ meta { registerField("version", "String") { "\"$version\"" } } -android { - namespace = pluginNamespace - - defaultConfig { - compileSdk = 34 - minSdk = 24 - } - - buildTypes { - getByName("release") { - isMinifyEnabled = false - } - } - - compileOptions { - sourceCompatibility = JavaVersion.VERSION_11 - targetCompatibility = JavaVersion.VERSION_11 - } - - kotlinOptions { - jvmTarget = "11" - } - - publishing { - singleVariant("release") {} +ftc { + kotlin() + sdk { + compileOnly(RobotCore) + compileOnly(FtcCommon) + compileOnly(RobotServer) } } dependencies { - compileOnly("org.firstinspires.ftc:Inspection:11.0.0") - compileOnly("org.firstinspires.ftc:Blocks:11.0.0") - compileOnly("org.firstinspires.ftc:RobotCore:11.0.0") - compileOnly("org.firstinspires.ftc:RobotServer:11.0.0") - compileOnly("org.firstinspires.ftc:OnBotJava:11.0.0") - compileOnly("org.firstinspires.ftc:Hardware:11.0.0") - compileOnly("org.firstinspires.ftc:FtcCommon:11.0.0") - compileOnly("org.firstinspires.ftc:Vision:11.0.0") - compileOnly(project(":Panels")) } diff --git a/library/Pinger/src/main/java/com/bylazar/pinger/PingerPlugin.kt b/library/Pinger/src/main/java/com/bylazar/pinger/PingerPlugin.kt index 274ead0..159753d 100644 --- a/library/Pinger/src/main/java/com/bylazar/pinger/PingerPlugin.kt +++ b/library/Pinger/src/main/java/com/bylazar/pinger/PingerPlugin.kt @@ -9,7 +9,6 @@ import com.bylazar.panels.server.Socket import com.qualcomm.ftccommon.FtcEventLoop import com.qualcomm.robotcore.eventloop.opmode.OpMode import com.qualcomm.robotcore.eventloop.opmode.OpModeManagerImpl -import com.sun.tools.doclint.Entity.delta open class ExamplePluginConfig : BasePluginConfig() { } diff --git a/library/Pinger/web/bun.lock b/library/Pinger/web/bun.lock index b8db93a..0d865e4 100644 --- a/library/Pinger/web/bun.lock +++ b/library/Pinger/web/bun.lock @@ -1,5 +1,6 @@ { "lockfileVersion": 1, + "configVersion": 0, "workspaces": { "": { "name": "panels-plugin", diff --git a/library/TeamCode/build.gradle b/library/TeamCode/build.gradle deleted file mode 100644 index c9de007..0000000 --- a/library/TeamCode/build.gradle +++ /dev/null @@ -1,61 +0,0 @@ -// -// build.gradle in TeamCode -// -// Most of the definitions for building your module reside in a common, shared -// file 'build.common.gradle'. Being factored in this way makes it easier to -// integrate updates to the FTC into your code. If you really need to customize -// the build definitions, you can place those customizations in this file, but -// please think carefully as to whether such customizations are really necessary -// before doing so. - - -// Custom definitions may go here - -// Include common definitions from above. -buildscript { - repositories { - mavenCentral() - maven { - url "https://repo.dairy.foundation/releases" - } - } - dependencies { - classpath "dev.frozenmilk:Load:0.2.4" - } -} - -apply from: '../build.common.gradle' -apply from: '../build.dependencies.gradle' -apply plugin: 'org.jetbrains.kotlin.android' -apply plugin: 'dev.frozenmilk.sinister.sloth.load' - -android { - namespace = 'org.firstinspires.ftc.teamcode' - - compileSdk 34 - - packagingOptions { - jniLibs.useLegacyPackaging true - } - - compileOptions { - sourceCompatibility JavaVersion.VERSION_11 - targetCompatibility JavaVersion.VERSION_11 - } - kotlinOptions { - jvmTarget = '11' - } -} - -repositories { - maven { - url "https://repo.dairy.foundation/releases" - } -} - -dependencies { - implementation project(':FtcRobotController') - implementation project(':FullPanels') - implementation project(':ExamplePlugin') - implementation("dev.frozenmilk.sinister:Sloth:0.2.4") -} diff --git a/library/TeamCode/build.gradle.kts b/library/TeamCode/build.gradle.kts new file mode 100644 index 0000000..497754d --- /dev/null +++ b/library/TeamCode/build.gradle.kts @@ -0,0 +1,12 @@ +plugins { + id("dev.frozenmilk.teamcode") version "11.1.0-1.1.1" +} + +ftc { + kotlin() + sdk.TeamCode() +} + +dependencies { + implementation(project(":FullPanels")) +} \ No newline at end of file diff --git a/library/TeamCode/lib/OpModeAnnotationProcessor.jar b/library/TeamCode/lib/OpModeAnnotationProcessor.jar deleted file mode 100644 index 4825cc36d9606badc3c0eac0f61079f9c5a4d196..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6032 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 diff --git a/library/TeamCode/libs/ftc.debug.keystore b/library/TeamCode/libs/ftc.debug.keystore new file mode 100644 index 0000000000000000000000000000000000000000..a7204e66d948a1bcff16a75fe0a85f9577ae675e GIT binary patch literal 2146 zcmbW1X*kpi8^-5v24fj(#&%>WSsLa)3864$DI&&@?I1f@l3iJ@X(CIZK_eW?*urUK z31=)JY6u~UOd$+eLX@%f&U@bT;e38SJRk1+!*e~)b3fPbaP@E%0)cR_3;4SPuLXJD z4EFQ%^l}gPx$YIs_HpW`O6U*>Cjb&)2cQ5K?@2BI2B>lf0{|xkMu08l#)lw-&_qoa zYJ-jsL!5EZztm^+U7_(aIejHx!DiFjxU5UakXBUkm-T+E5(~g`i+BJb72ER%9Em{HaEQ> ze>hLO!;N9ICSdF!@4zA?nl4j$SZk3ISx17_Q-qN53Xw6@#PJA^&m~Tq;?T~);DHpT zFKzDFS}i^*@GWkdl)O+D`(8*R<9D?gQom_pnhK!_E&1ppZ%V0F-D2_q7uTyUk~Wi` z#8oe`X>;WxkWe<7S=q3sH2f_Zr`DkED;??9?y6D zg^D9a6Zsk6B=7ig=Lp|8RpxSzsohzHIm55INIrZG z0~}nUx<+=Y`N9poN^z-;bzj6^ytwmYsD7uvdEM$QH*Wt}$=t>Xhtnh>kYKV%)j0N5 zn-R^)7`TdDs~{PD675#GU$)}+NwIJyLI*nM5w$SxrE2yR%go;mj?>+q28A;8?U8p0 zC|!xk-IXEBH-nT*%t*ZXmAbGag36f-O=%s*edvNwYZvhi=poesOX#8=0~Kza_AaXSPWf?Ieg;kGO`(Y5a2FjV(2~ z)$m)Otv4#{*2Y%*$W+CSM>>DEgvOxwGg7D)dUWQcy=CZ9lf~f>^`$aNon~tU_uW77tnNj+V&lnV zOR{60cQ45UrVJmS*ghJtZ}$KyHS`YGLW(X3?HjRz8s0(m+e}3h@a|%_xS6!-{C(~poK#X1&8I=6S?)C7 zp%2lmX8Y6k4Q`198}js9sd};6$Haly`0=3%+$Y;Oa3-Do7M=;zqzqdr54)1N98wHpDCM2i5 zx+bUtfk1^p0u%uffbAkE2f)Drvw58g2N7I6s)-j9eYj}roFWB5QGE~qVxOO(Ub z1|!7ACtL7}a%2C8Squ_9@?QrkiulKoGxB1S7d$GCz-WOO3U8M5z-izl)h%HOii*2Jii7vcK+VFZeW(oQ$)-dM5lxB+F8Rg;%}Z!yuomfXaV zo6mTkYmtn)Z4t1l2~o^@MQx{SPxkpUi1~)$_swsOz)2AD*K+Rz^ZmqCBX>sexv->c z2iZ7LUuk~Ag`<6FiGxYrIMiuEPb|Af{rZcyA#bPR&tIPQZH)`=(2+SfJSJo2a>?T@p`}+^n zDHY#vn(rM<%aug~tT_Ehr3wcWfB+PEP!^P72O}c@L&A=H54pJaPN&F9?H*6~WOYD= z^1CIKe;<*ZH2a7EB&s1TJk73f^r*`0b*)ytmzrev*v?a~r?>Nm7nyQ$S{^u1sr`{Tb94ojRBvAcpr;16;$-DiEi2 z-M1pWJ_JwFaic}UVbSWf8Ax|bn^Amp;bGDZzBJ)

;VVQ7wbIBbW0D+qhBJ^gTTalgL#*k1Qn|X>ZHTwmF5GOd*oT#SgN5D zj0|-L33J@uQ{zAt0pueb+PKI zYJ@r-%F!FDGz~}dMfFLQj5ST9x}v%a|3HiCrs|S%hVr~>jcU7coN}l-Kpm@UR&Gab z@_W@@6D4N&mGDCp2yCntg0lr0y_rnLIB z+GHPO?A90ZW3s+78?uQm$R^8{$=b+_vh%Vz@Omx5+}zx9p=GChpnSKigDgqKfF-gZ+5cK5v>b)k*Mxl9mGbuBotR{uTp>FmTO~|PgW+oEIZdSy=9-Qm+UckQ3vsF8)XkFKsRP{XjAn&YE4?GVC0#D1gD2Kg z%9XBeN@^;W?vajaYLtY-NpoD>U7RTSEw+f4i)G?X;#=Z*;u~P&z7h`=Gej4JD@0+U znL>tep@;)ARu5r<=(>=D=S^Wh;bx%&+Ji0)NsT`nVjErvQX4-ubZn3dIE`-_iW)XG zENLJb_TfA01!;ml4b>p%D(kN_WH+1@bP*^8JCIWHq~2J+xL#You)aG9ztcrpVJEDGJw&gBPSH-0UUW%Bm0T9j5Oc(A z$p^7YbWIc`euWfIjwnv_Ryani5xo;F7llA?;S^sK*Gi^INbsjbHwh$eU@4bN=1T4~ z?QghR(CVkU`CMmeFwM{#lo;K$<@06xWOPY^1vs->O50WWc zj>wkFT3ZTQ+Q~{=nvt4fg`Ox~<}I5lZ-yGOwNO6 zu}uC%E@-{qIskpwWJO)8R&i6oRZ12A$I)4UM|Evcc;?D@l8F*XkRZXG6n81^TD)kn zLeT=nU5XbiF2!Ao6?Z6BTtajvHj{CEf8NK}#wIs&&popD+H0--0bN*H?)Kc;^d7F0 z7m~NcGR-pFTHm^bjsvFr>!|K^`A2Q*h{@A!-N=)-p>MIlo}5o!?IN!(sCKUOZ19|f zL;ffAob`b%0dw$O@B{uXdZ{Qeol&Yp{iU?rMh>dO=!nx-tJ1gC$Ab`0nDDQf592NYt(eR(% zfCh%brJM(EVXS?my(M~tXK0JpfgARM7dMd?`lmqNRQPDP#bYuFLE$5lgTg(^aN zgftAD61odE;t2gc{Slo__eyIZ+JB+j4W`@{WNQfhg7P%AwF%@}bBQ~0z^r>{)@b_R z*{jo*()x5s`VCY}iqJ2y4>{bX5Ld|V&=ioG!C?o`h8#A!jh9eW?u`gU^ou+Z`5-bp zDkZ8~bjg@$F$1EHNAHi33Ta|S#;l1k$85nj{Ze$1nCDTYqPs_Jh{}&_7S%4|M8xcf zyAjunLU?p|@9;@s_rn^8Z3)|i-g#q)BV;0dk4CROlmAQZ}S2pjEC7ZVDzU8Ys#HV}s=sp^6T0 zkLCtv2EPPv2Ak4@=eOY2;LmXCOo4Y)#WMYU$uBPv-<+m*N`+vVpem>gJ_{Z}Yh5U~ zEBHH^XEAgJhZT9jyx@f3NUl&bc!R#76F@L$2Koigq058C!Ec`qjPVclPk^PT;;dZf z`vVo^GT&LxHO~}y*el#a-6yCY{@`xm&T#$ZYU>&d!!6WR-Wlf{NRNq9jxfh~dndBu zZuT4br}8`7s=^3ckbfk95)6Q$bV<&z-pap{UlAm?z(-?)b*%Lb$nFcvVM|TA)P1wu zwuGRQyaaDousq2Nu_jwi=Pk$!x4yMZre|nVd?^ZB9O(ZV=H1KNop*;meCPAV<&DX0 zL_ByAht-`q7jh=%DB(D^%U0oSQ!)E!*66IEd^B?o4b4i-s$_m`T54`#)|>w|9W?)7 zzHVM+4mUqGjWWlYkK${25O0c&nLlKHGOagl&8(a`(Nxq_ntd@?)AWRhPOlrGJX~+o#lFsTp`Gyht0Ib}+Si z>iD$qv`MKEsn1edrFOuNBgpApEB$3!+q93V8+kXE)2614Nsqzt`ZxYkkJQ>}x9G&N zH)UyBwKQYe1^h#rq#eS)H#F@CKf93HEbVjZNxr8~+nQQ6{cl_hR@0TBBj@9cl&q9d zDV^}Tew{KP<=@m1sTES6q{wOOaJm|pQYCdO{E@;iNbY=FlUj*8-H{TPwmJ0-_w!q7 zBzJf|Wm=jwwPBhiHNg2>DfJrn^XXe5cqemGRjCKkTBeOj6VvXej!Ny5T7us{o^q02 z1j*^E(-&t%W)x#AR?!3Ue){zEBk2{>&1eOaGA^h8lIh6!24A5DanSS3DVgtaN116Z zOKeor)CRA)H^fb?>0J<(eI#o{wj%plR+p^p*-6>8bN``Z=DGEbkKi-JWCob`A~KEZZpCLHi%}R`3Om*r(W2sJ$Em zwZ89M;jH1zcKq$0=pNvz?RxB9?cMuJSCl!$v2o#+yD zMz}2`fyWf|bt){YkMQCC)m(^d?`!$a=CF!Epo;v$a+DY0mnkkxpv`@6- zG;1}r!1sp0pfvFBx%#>KhUz(8>{hDGl0&M1?zElMj4Z5}yk6Wcc7dz&QSlheFCV^u zKTrc+YgMwiOWwb{wWziSJa;^U=;i#tbrAM!1J`BvzMY+k&K{thwOr9ua+6$1_*{>o z55-I8NoO;c;JSydVxGIZyNb66+~rxGDrgA)AU~<@)p#8+iuP0Ie}tNCIh?H0{u2Jv zzFWR%e@XhBCc-N&HWMP?Ef4(x)JVv?&t9J-h*Qf2Fbia@5f9y zYCpTXx|?_mo@#ELXm2Igng>UF4x0IMFuM%_DRA1q*B|eX zqTlT(|9JXS{^MWbuRx}^**}Y@r-%OqXy^erXRmw@eXYpLzvJpIznu=+cl_4^ivnHX zg!H4gW8YxW;9+{=8WiT>{9sRT&*5-wdXQ%iRXhmZ5B8=HLn-n!2!4gV5U==7 zSzCFY--=Y|6f@D+O;-$5~{s;c%`i;xRjf_XDk&&b?2)k9|9M zCQs4stny{!U0sj-@`Y!==Z*WGySO(D6d=cAfm=G4T7EC`)qP~FC&(AWy?XB;w3Z`% zm3*6d=GXX%=zZ(4dY*e4k=eH9I*%CB)2T|?HpmWzu=V(V4M@dJ% zeUziVBhTq^E^>5p{O#BYuD{z}A3di?wQ0L;j_o05_`mcw?Q5@LZ(|c}Lu`?@kJgpe z&G|p&huNOwAF&R$9%a8sroY?~TVLB_1u`ekGW}+V2|vDiAy z8q4d~5LX||U7kA!+|Zo&J}=hl!ojPMxIK#q=b&w&Z7q0d2sM;T zutAb+QMUcyrrQ}8y~9t{y9^bUs`m2u;%u?cw5Qr1*w=#hm1nf|4v)Q~qq)Ok`)2E9 zuVSBTYi_IHaNsoX+AdIocw*mg??m2H(N)vc1;yNM-z(2z-B$Ih`Pu-LcOHzd$E z@H+4|FrT`fHyC8yPY+HZ_U)h;MjlgE*hSxj9IAbLl&6&4#Af1Z`T^E~q0~-jFPv6B zS9TTV37?g(l;dDreH1{5~HKq`~5E zV!EIfPm5Q@r@|xQ1Q^y#aH|F2>MkJ>#H*QDN9-;R7P<&Wg=@lV#cRb!`iq}L(bX3H z(k5{Cf0ce^BcX*bn*HY^1665IcUG(Yv z3I0#0aDK-3BZIotEXMCOdL*C=U<82QH2?$s+F^&p)0Yz|)#~I>H=z zhz~?rSh|OBEScwBk47)T>%z69h36`@s_ig?qr6J*8sQ52Fu! zEimQL{xR^9d;4ple_u@3g;sO~b_ArL!>5fA>+^hKRQ&&Rsd zlzx%-$c?CjAJV?i%DM_@Jeq+xEFixup_TOI^q+MhddU!H*r|IJ@q(T`6X3lsPIUx?gmMnDsHeW4FXAV!Ou17g<^4Op&Cx zH*q!NYs77in;7SgyB}YxXqTcJijFPzZ?WRV))#wKqE5p563r7zmE2V_H{th^+e#`* z9i~5=vecYXfl>!b4=QOYS*_%?l23~_C=n?3Q*md}g~iqui!HvTsK4l+g>^;V6lxW_ zIp%AODW*}O;gL@xmxn(MZ)IF&RMR(cU+A|`zplN$pKgJ!lXj!F45P9^byKwjP2D*Z z(j(=cWSPA9J!|a?E7&N-Nn_z0M94|<9_g&~2W*0e>RfdR_-}XBY3gR$KH5-SWt~GG zXYlIF8*b>5VT#nzHzP0qm0nWK^rQ9P89KoN_-be!GA3l2VVj|+evG~}-)m@SVz`RG zSZzZ$(21UgbbXY;rI!u+>F;@8^Atp24Hwov=K(YB$^G^$fVq=JfJ)c z_q4XcsHmz?D|)eFjX^PJKqFKm5XZkatnC*5z3hQyI6n*lf%q)f_s;)-Q{xwuF5#dM z75wvjLwtMb#q!0M?%Nz#5f}+}zt2C=Ux!%UO`Q6DpgLT+2;%zEeyjI|w~*ffQ)?Bm zcBn7G>%`0A56>3w&)zGbgr_*$8o71uQ|_5Ki-Z$lC%I?1y1-9p=UhhqQk%YpZCp{V zwbYcpQHgtuE@m?x>1CV_m^qK_R>v*JF!X*y$PRWn7CTx~yQ^xq*$jB^C>@XN%j}ix z6VSY0C4LedcX7s8Y@bHIFr72mfeO!2)D=K67;tQME_Nn3((Gd$wH=-Cn0slzXdeR8 zCjh6qx4oJ@6UUMz_8#`jwyyB4+u&<5*Y?0R-8KwO^(c z;~cf#!_I*y8s9rg5-+^9pR*^TW%=gVq_i*=gVpxOwGgmM>ySN2BUVe2|A*M}q=eoAJescN1 ze2uPTr^fx*)!3zXtwP`4A3ks%?k6AIXQ`WBcSU+`yYq=_9+BgC-9$H@-9+W5JkdU< zw=+3TJ+G1I{T}+5PVl8R`+DKG`I4Ng2+Ewzp3$DJ-g5A=EBXp@pFQ4j-ep^+@^5Bk2R?v&$u;alME(4;dG2cHO&zSvzVko6n^Z8fuiPg;VqbR0k z^158#6He~2zJ+ka!a>lh{=Fbm|2_(`d=or}FgTSG{ ztYG(GI~ep=&?634v{S?>gNo1eDVeJ1PUk(iAqs_}1AX_dDt0SY!|NWe?8Exoq`aow z#%Z@oSwpBSe1JQVqI{!lt0<>^=fQEE7VH)50~#4eW>$vx+69brCHFjmTC9%T{3o>Yu0Tq_0D{>*&>l6` zPXRX-%icsmgZzoamjiq&(H)5alfNl=Iy}{gh4bHjUJ;{C8`-iuk zr;O*hdz-tGyBPB+-*uBcq#N^MH=NRy_!C|AEGHfsfpfz~cN;WM>zKDU-RH>K+Pj;2 zjGij)qV5&$MsC0Bx$7<-5LLL&M3@)V+{NARKoSaE3~R%=n1NSZANHSRnC!bz-E~Ga zw%pyu-3M;pao1*C7;3rtxC-OMQPuSaD1_a0$yEfc?|2lg)!3^NskgTPo0yII_<(DJ zE6x?+y5^b!hEc}3*EQSK$yL_%&3VU}&HAe1ayh5D2e|#tSFA~u>!4${V=aF_iZztx zyy0BOI;-g{=A7r8;!JXcP-A)KypPAuTLT!SPk}evy!X7HydG~qbdS5qLE?x~fEF5iQ*eCMyoYSjAMF=yff0sm&|{BE4NJM;aP z)cb35&KOZLo+AE^3y>4I{#e%0RC1DpWwUeJ??$uJ5J8?k*vR- zFNtfsBn}_t8|AZm)ZWkhdjZ4y1-X@t_gsp2Je5e-<#Brcgo7M{r{Hepo&$dJYVR^{ zC0}JL z_^m8q4UF^l|37N69YkW5w>O<1OL(L4Woqwj$@{s?m1mM0tRxrM;jPPitI2pTL$@#Z zvb_bGKZ7raD>U>rL?PZ3d@BX-sH5J+-u-A6)m&!|xW_SSb88v9@_0&>f~76;dwW4t z+IU)f{_$?|&Vj$Wm#15mr@M~7Hk7YZ8P|e*(&W|q64-aQk*&UjJAdDMo0(A_-ga4E z7jJWKn%Cle$l6`X-5l{8^sMks_nzeAX;$e~&t?#{GM*aVX5Ms9uIC(oOM+9do%NgV z&TxMt$2GfOyPt!p90V^}1ovh(=!wl~b{^$>^ZC3OTFtt66vnZdYq`6)pR%4-<3e^3 zZTU6U=p6RzU@bQeft^PJev)_*N?U%ww?AloW`B(w>afx;)x1&ExJ;heU*KL{ZGey$3>i4 zKH=~Biu1g^Bhk^&QIyU@yX{Zy=k48WWo@k-wHzgh5uV!j*p2kEbLJn*pNkKKC;uLG z!#%JT_u69ZpKY^=8LHZ1Z5yl$tx5R_ILJP<&ZQ=^E`Le>V{~9S)~nWdaFR6ZOKW#a zSxa}zcb4jT0*K5{ATsH>UMev8mirbFtR_G2Q{JVVWjTYgt7iXVDrDMknqb-ojxC#R zWNyr8m=VnQJ7bG^rn#Cqi>gN(Q$Ktdy3{t_ zw%Iz{`W#&Qyk)(mq}5?5j&5x_jI9Q_DPZCK@;0E2{!G8By5Qtjb9U#v&fS@N0(|=! z*wYJo^R2b?w|um2grPOn`o(hB63Xv4hxax-zgvD9I)sU!Pk+$o%7?eW99uKnY8>Ty z+Ku)sJ8>bA;_uF(&SQ?1Ft$fKHZYG?y9a~TRU~Jg;@;dQe}{FedcR&;=oP zLUx8Ght{T#+&|%w5g8Flk_MMOuiCSrjPjXj83Qs)W`2SHHP_tCd=x*f^SSeLFIpy8;_yTC&~s4*$6f=@ z{e<}Q2w3?Zyb)K!alGU>;n-7up+8Dfr1fxc>%$|Rt5~ck&@?Xc{p{Q8 zyX&jZ{@;K&^9H+`=(Bjo`^Nb0z((rrY3TWtb9cRK7x!_9y=0H0I;d?0d=5X`p4ndD zyET+PLQ!b=f3~%^J!C(4W8X_`-+&R{>xe_Kp6Pst_r*+in(MLaH_sYQ)K#8)p#Hr$ zNsf~TH7DkE;Ia3W{ci`JQ5T#KoXvuTZp zO5X4dKY&T@LGEYt3{UZX;N1M#7fSB7iqUX@GESuiqVoAU`5Jm_`*hSLW_uyTaGp6m z4!7U^J$P0pya*FLanv=+xvP3gdk(q(a94&2lH_jYp6Tr5v^x*dX>x~SptG*CH~Zl; z=0PDa)_IQhWNZT+r|Ijv!!d|`c)R_eb0Z3j4~_`(iCNCxxI>+Bo^ma94aaS=Kh>XR zc*8a)D{Mudgw9mDnsL3UbORoNwrizrnC&Ng4y)xy=Lhp|<@W}c-e#L@`vg|J9PUal zYeQ>AQ06*tH}>cLlG{5kng3VI{gk^a_ef5koa;FY=x&gdvpKsfTEwzBzhzg>Ud7yd zoINpnX-?gogW03At=W6v$xh1F=KPucIcIs!%j_xHnK^TEGV!=e%UPLoDZ69#P32V2d-8t^HbBGbReB;PB6bS|7ae`etQ_+)c`MgxXYOxuXYQdEex27eHor6VGUu4Kb4qM8S2d3_3uZglZDz{K?8oV#;GOQr zS6YD|>IANH!raUJl$v^Y)=jSNGHuTqn5E5nYTlXEGOH@RuKz`kmzA|Y>sHq6tTfIQ zMb;IwCi{F=p81%$At%Y5thrgXtixFovW+kW_QDaUo4pE-+hWGwNf*SfIr^M3aFllE zbjrDrGa=_`PQRR}+;=&B(TNV{S>zhATUn1-I$PdZ|FTZEMqBS%*I66p2RLyD zS?A?9q1Le88f|;Ve2U30ZwusKL|b~sw#+8m-@<`&RUB~^$hYv_ivZ-?ZL;lm? zFZsR~{!9Mf$-VC3*fbp`@Fp}zX@Q%8BluYE#EEYRz42avy!wK!U?04p@)Y@Ghl%h^ z7vPcbhoXQxx)%=2YJ3*zfT`>8v~EpbAX%s+tP=N#-Gxs0=zO4K%4l&l-YjeydrR(Sh zIZw)#Hp1B71I8ud67mBcfo-Wkua!5+o#bBfS6QW+D9@GmsUE97;i&mZWmg4dy-HTc ztN%mq6sszR#>s<*Hj(PHUA2#1MKjPkyJaIN+zQou*)P}P-+Fns`~zBn!Faz7kw)SI znT2*PmUq`iDk8O%{)M+~M{}?i|Ab_!~eiTE3h;+fqD-lBprj0}Ds z3iFG>6?l2=N4K&ka3HWfxFa|&I0%-}3979>2UntBT^w8n%HEXRz8YGDsNggF$%Y0y zqU*>HHpOXHfPr*dag@wIIrtwa!8UM>CEyG`yigy~8^Xl=m`;s11P;_B^etw^JH-ar zPTSzlx%uf4#cH%Lm#Gf*q4R@A*$7mz68E(bElE|Ox=>wN7iWoNpzlG+glR1=dwa~!^mqmrfM2JwFKL(rdNoa)I-YWGrwNoyl5?D)?kc7~a zMw9Vxyyyp`N#7muQcu9oZ`S@95XDy$f$H z;xDo@u6TTr_!aST+_1RiasIdpaa-e-$Lou3i%W=4jT;c(Gww^AuE^>lOACK0+_KQt zLJbO?D6}l*e$3q>VqA;(^YLTj)8k{~m&bQ3x~J%X;;!O1i?%4HE4H{;T=CV#_mnDM z`cav#iE(9*lvS4BQGO>Ka_d#euhOeZ^7SZ`udtM9hr4LP-(s(hE?r;A&nhsG>2JTYFop?Z+-3^`G{AJpZ-Z*9V_xe;)U#?5DY(;y#W1V*Ik?bJFKZA2V?AN&3+7 zqx3O8<<_?&^f@SOO3i#mkLWI$ip;vX&YS`H-Sh9-AK43Hcm=hBtMY5Pjru3`GS*VM z;-O-{cvk!%y_6!Q8q&|;qJQB6F@zqb2Z)cq+P>KQdDrr;SQc6i&~s)zJsHZ-S9~}c z?)mf`ND^8LZE&5Ks@|@?hu2zVSg&s)%SJVdxf#>H(1AkDV{gZ5aLcV9e;NnP-1r{F zZWQZK>_M>x38xYYm0Vr2d&yJO!iJVyTOv9kBVlDpZNje!o)WVXUKjnL*x|S$@zJqY zV$Vm`jJg?GJ*=5_y7r9fKUFLK=5sj64ZK~wuU*Gn)$l`YORhc=Z0#OiQBNJG9QTMz zr{j&?j<|Ck`QR4ZvI=<$QLVj-E8lf@F+NXr*<4-Vet2Bb?s;Gi2c3U9GeF!0`d;;M z{N(5b6QIAnp8cj{2|2<}m>n$~qu_IR^55i#*sS^Gi9_0;OPZ3?Jtr*ZRrbd0CFrjr zvL(DM>Y~FkTKG2x*3}Nnrw}z%_1{x zLSOAloU@0d`_eI<=RV~z<;~z%>f*b>+#dUH;9_;tdx<*DT~CVVF#f+|@G|d%Pf8x0 zq3+Ndsv1acxX+09tbVX&Xco(Y~rSAr{y=zc3z znXN=h&26=9HDPz0M>F;dovGhj4uM`A%}dXFl6O9Db>5!5fq5n2ow{-_=l%_jkV`DL z$x^{m#QMk*OP;kdzi<99>M?Whk-g+t?>Nic>+de(9!q^{wtt$xIH%tRI1A5|x0FeE z0k$DtnM~~QMf8eus3VV+R#16s!ufDXEHB2Oneht)sW}&%(N={Azo{$AlFHK5q35B7 zo{p1HfrhiP@F(bfyjVmWDhv}Iacn9!%=Se%H z2DnYPl=@4>Q8Lbu&clE2BA4guRd}d16o-hJC?OlNA6{0bDFd*k-cy;YhNn!)|NAh% z^#1F9?kwKH2;njn;kPr86F1pTD1>u7(Unu#oXY|U}N-6ce&Evys8-* z(4VPO80g8+nA5ER@zpiX+$0o~Eu@}OQMmt`JQf>>P&z%U#g>f_d}0hy!&~aX)#ZN7l{)fRxuY~$n#8y*!}m~+8dfQVN>6YeC@ zx(mrd7V7FHL_#m9{w~KC?H3_LtRc3<8>2oeVHS0$3BnrTU*>!dUUycwFZ5InRqhpT z;xw^; zT_sAZiRCV`uQd_J^0#`3jhRDF1gr3u=%EWtZ?PQn#w&E?3nf@e2%S+@hFnf2% zUnQ4xSyrfOO6#P};GR41lpc#`#ZmIP&r+ytkn79$rF`j%<}ADyZG!O z^SgnxmT?;{9QGIbABzmwD-@tv8WK9A?o7gaqJx}h2co?8>u zch!efW_Ths$z6M@hpF$V$EmldN2?C0V%0;``|wITg!5A~)n;DPL2Xslpudkn-IHDu zL-DM5ppMiGhhIIBD^Gy$5~6Oa4)PAxsNUfk6{l`R-W!GMOs<@uny-FE&jUeSl_>wP zO2zAMsLH^D`AFtmpboms_cp6vs)wi!sJf_jz*gz4D)0zff``XzaO)}R7wY}0Tvb=z zUl06i&hWQat5Q^>)u;K{W3`cM-BK6U)KCvlm(z6Byi-M^=o+LsuKoZcsIumfI!Y6- znFR~ygGSOc)O4Wd^C^`^JxX(1vqb$`y%|lEUtI-1t3{d@XqTciJ$O*4(_{H+PmHdXJ#&8EQHEEY%U z`eZYs)eBYk@XuPI?yK61!oR8dnN&n>PJVnb-uDqT=0MFH zO(;25chyi;N#S$H61$bRm5ZqTH^+bbK3UL2y{7xW3{! z(8KxGcHh?6R>~$&VR=Mv!Xr`box zFkjm0Cg|$%7b3#Cgw-)lHV#5}cQ#xdu^{4o#K_2-k&mM)N56|nD&&rCLR^2UP?JK( zp&pEiyQ^mP?XPO_ zNCdjn>08%ZH?!X9`qvsJSwfruk_M#dMYi_F6srvTvgDSi)I=xtu@u2Z0&P?kI@O;w=)0UJ8DZzL9 z-sQji?WN<@#8>~k{{Hpje+T^={cz-i(NESr?)v8ae<@$Kei@w{k=*H<`dh>F@bvOo zAI0Q$&W;*BgDn9BqOE9n&FyyA!|aS zq9;YqEu39=PP`@FUt&f=QsVB!u@$2$b*&syrDOHT8b8!3pR}M}&-#-XkFgE9Hmq7N zrvAh4w$*-B>q^qGD$}ZdF1M`wvJ#FGfg=@r_L9KJ8O#RxdS z#la3|f-8Oq*wM0w1s?{U2lm707=sog)Sd6@;A!vq50=nT;?(o5b@bf0iK3&Bv!$(q ztxA4aeu18POWttmn2Nkxx%+a3yt}v+G|X+9S0gVe*O+?+H;O}9i?dc`jm+w8u4vYq zGBbamTXxUXQmNHav#3?w`1UdNRO+O(`e}b<4$AD5Bjs$t;h`sdCjq^HC8$(v=RByV z9j)yg@^i?Wh%%9X;q%<5$o(QmiW-Y8E*?{2Y=S#sNr{kzPYDxCE-KL?p;(DiCA>wK z7i$%t6>p5&7UxQ9kH8Y!szngDYSro0j8LKg_2N1HjiE#y)?=mwK*a@@<@1% z2z}V3u+4a@CgaMt-0;RQiteUYbdtV8ib>siW1aJHi(X(Y%u@sGi9pJii)p z8mDtC=kOx1j@!Y)it!-2b$tDN=`PWIk?d5-SY4w=x+iOBR*%d^ndj2?q@PP!nNl~k zTx#7E&$qj&D^kPLa#B~JFN-iaGjE{yK9sXRCl@u|u>1!3?`$`0)mp4v zNcbn>(ir+4-=|ygF!uLj^vCgN8lx9&sI97-i>INIZY+Q4KX5OVLWYMN2=&7n4Gn8y zTxmQX<_bG*%rvetrW=AX1aqCYX#P1@uNBkUq2d}k$Mmh|jHXbNc) zON4{gUZK$n@t&FV}{&ny1vTI^3L#?N$^-s$_iR zAob`etg&>fl>gcK#9H6h()Kd{X?`yJ{+qUgw!g^LOVdTGr>l{x5Kh_W-8bCz@vbO| zS942vjYIkG@4%zLQ)1YWin)r`#H_QaDelC9crZCz3b^*a_}e5(t)wy1D59gzplP*L zeO14!3Tv99oD#Km{XoM*gBVgVY)ROD#>EicGdwh64BboXM5ab|kD3zsCDInvH##S> zMpU7w5mCy>0g*2vDn<5)SRXOp=rrC6i!i`6_r*{xma4Ovs?&H9kf*!{V>NXq4jc@+2Kohh@=Y4N{9-N^Mk=M7Ts&@%qOj9tC zNpN5+UIRU7^F7J9?}=azm#7E!#edJ@&G*W%?LNQ&sfUgz)LWgt1KoYc@ah{&kBIs( zZ`RVMa~&+uT$t#W*q_RwEbs;kfhs>#7=&iRIoy0$!sibJa0mE3$1UEVH=pTw1T6D5<$-^aITv@lUvq*zX;xt)rE!KJ~Afn+Ku zCj$@gvycK60%vh`i$z~G+cy>Wi$lJ#-i7dS8o?O)<{iW5z5FBn_weJM5||eVh38Z; zSef3Adc_;C*N5bQxA9KfD()px?!I>Lg7K?NqHq zUxI&iK@go}+|=IbKI#h_@^vNk4t;e)guXSNxgTLouQIeXOf4Ss;nG(pWvTt+{uGYg^? zbvMSBj8HSMqI~d<8}wWGD0~qFAw7(8aP03@_%NGLI zQIOT)?$@GYUXHH7j;5?*pd=Z0Y_J(4x+GW$r|nAgcQ)WF*IqH1-1xpy4exO(o+W}1 zM^^Zo(43s6E&0wY;{Tg?*PW7I$Zv6)TaI7tKltDsq2HPanpH>rqq?K!7mZO}Tm6(? zQcYlb?*RMFhLyfZyH6X`*49;HF08|C_%uDco9QR&r{UCC6hD}3U0wZJyoBq)Ro|qu zgPTp!zr)q_58Wf(2d!Q=M7vy@OZrwB2ZLD$_e4UIb53vx&qcMQ7L&wB)HT+K zPsA{MAdg8}vgEgnOo+-RmrxaW5V&MwPxasUUHzjfM2v7p?N(PK_I#u>Oy9nBrlgMQz!W#8sMgPm4ZSCp6e{JA^ARz zhvHfM)vMqV_FfSU+IEh3^BO+KF2w@nS>;H2edK`jn^59k<+Bta!FjhpC3CD#<6sZ&a04 zvs6P_xm8t@Rb8pct|sm%#(w;UdTlB64Nc`axU|ogccCFJjp8AKll3fkOnv%1T0S?#HGqFYHiyaaAo0pSrZtP3PQjWesI&url6k zWtDFgNy-Iya8ID0NKw$bc6j}pgQXRx(6K#G_!O;_tLY^-RH?!9x;;3}BV|CDArujR z5GDzu`MYDlAwLQR@d#cfM;VtL(iyz3(JX>z!+U<&QQiC%+v{ta#* z%U#(oBDvSbeEnIjtQxKwO`n$Dc+l12b-h(vR6Ed)6y#Taxrb^KweGia8P!zAaRB34 zS)K@HT3;2;eCYtrQt%9VgQgti8TiFUoFYTy!i?2H*+M@iExla_$cMp08cQQklsu76 z@_v7m_R=5gJ5ftLvNn6wGoiHDh#Klnu)puAnl=>H32%g0@u-k3v=vu~=LL^Yhuq?o zSXL^)k{*gW$tE-bZE7Ta5o(jchDiT`<-7t{nk5|u*O77Ri;=y!m>#1V8W3A>dSv6@ z7cIXBhgpE1-dU=p7g+_v!Lau6wTDsa%IQ~vYQ4;}56XR270Ge;f?e$8=N0iiRnj%Y z%Lo<2pYJ3d?>AHt>Ui*}dU#5nVSGNxTGbr+JqXSfP@>wZ#_|b9ypL)x)!W;QS-RAg z%86U9${2pnh&AJN3J}CNuF)7AF$_$;2&2@Tx`2v`?o2%Xl5i9pM%+1&*rgGjg_eLx z&0_yhOMRu!Vzg9)`8a@UEfT-Lf4C^Mf-?{f|KXZg8I{^EAZf`Ux62uaFOm)&{0{mN zO{6BEW33#M(y0&!K(AWkQusZU)&$i`M(!+o_6p7u4Lpd|%$76qM_Et}Qf=kjbIT3D z%cn6TCaLP8aI46@A7Ra8q6mtopYK)Kpo*lQN>h0ubLg0=x4a17!zrqM@*~FmCNu4> zJV{=|sZ~ss3(DD@-L5bmy1T$hJAsf|`CfY}+Ly$5u*})gd8+I#v9XkZZgUs?F1m~J z#Bp#6mx(v2;0_dLv;Oqd&{vD16wiwM0uI`daf+52Nr!lSN9GG|*Hl+}fcnjZ7t&W; zP5tAJSXzqWyevXpelKU1n>Et|71NIevxKu?IUmb&ozrkJuJQXTSYspjz3;(RKQXt; zgBebxcb^E7nMViDOwjQstc7y&c+Qxgc$V#Co%9E zr#TRh`&0bRGWovznA&zNPR!F_gVD^724L+Yi?{OHh9i1rvKm< zp(W8wL&+y}XQ!_t{m#l-#b~8c3wp%roGjgwntOeN6lYQaVV= z5-UpGq?2Gsxr|>ud@ySe#lGV#Q?l<|gpIS04oW|Q>b``<@IX4lKJX{q0Yv$`|Kr|1 z*5Xy}_BZC_XQ>q9)nA$^UyviE_KeMDc`vnxpNO#rv(L1UXR@Y&#Jf! zdflCI>qJ#7k*DKhEmY&#o-OFmB}(iY2RIRLODX)Vm+b!|r1iX4y{v*i5e1hsnsvGa z%)UR@ngYuIRH`g@;94?B?^jX7Q*SJfp}tvE9w@hg*HInR+m9=S7XQ3I*fG-hNe6K3 zOunb14pp4}yC3^c8o2Ucp5qevusn+>Dn}l!nkzpe3%$yjoy>R~mJ5@|oR*x@33B+) zVAhM-tNqew*(Y^p)xD8_XZB5FkDt$6+raAn#2CK^|L(x`n=nhBb05XI*BDk|9HU#6 z88@FhSjD(kmWRq3Mx~)VS>DOr7nPe+H(N;+qX=B7>Tsy;F^gCHf3E+^`FWOorYS4? zGI)H3R7;L!29M*(w_tXsFj6OoJ#K-KA17XJ&g%?vBkrP-syMM?B~>qCk~%17i*i6E z(Gh$)cN#=-SfAB&Lk?#QOR0LOlqeCuf|IARx|S2S)MFpoEB(vrJCDc5&r%Pn0tJ7+ z8qcK<6@*>v?ax?GUDyX3!uvAwJW9z;`~|8b=;uN@P(Dpo_cPo_%T zN;$Z1_v0O%mS>~DJkRrV^2(h&>w)a%Rd|+VxVJPoKW5g`FwWNH%$@_R!&+duW>$nk zl|)RmxnOsZH*)4!q_P9@RqD%q*>hU(zFtTxcvYrUoH4IQ zPmVI|^~32r_JispQKXVMYrcF|zNiw_hlvorsj8`8a-I$X!^xImSI7tWsv~D$K0&OKG}_#KZ-j@V=vtyzmnZjd9MCKW|wB(Wix}cs`EsM zTjUI$_%{A#sOkK-dP&4Gl@-;M@wqRjGb+VY1@o#dI^&_-LnOVzDlrRFnVVj@ zj_ML?_7P|Q2L8W{=kpJ{e=_?~dEP}L=VeWv-6CfBWMa#^{A?Y*=nr}Nxr|;Sd)RjF z;)s+g&A5Qsfsz^VVD^xV+Fn`)E=vuw;0&Gu(=Ht@V1}Zk zQg(H96$43bXRZ!A*=SD7xRz1w+qdL*U$^4ysQSA3YsatB=Yya2d~WkO{M&=%qoz@& zkCu;?skRBW5%dWi2E(O4n1hDe(J=BjQIn_{8FD)0akE`H>3waW9A+$)?-(fY3vqJJiehz&T`XaPh*dTB&wc&R|JI!IuB=v1| zVJdDrsUa?5d`7Z%`{OVByY!#*f!xd&tgF}+_!_8=?sX}Bnd+ign2Yw}6ka7S@HXj- zPf35?>7MG^X>Vu~wClCYsO-&BJyF%7r{)0Rh)_{EP^qUUtQ7ny_&xl)Kk$~_5d1gj zRF)Nb;OFog&#sz!gSvzEl-8tc4YKk`?+NJ}stcVI+7vWY3>zJG+E^%jSj6LqlM$sO zLn7u!oC&EMTHG+h@J(A+cU`O0?MK6ZgSD}p{pFb|45huOS;0EqLu}KIT_px@sxg|2 z)E%?cc_3%A=;<6#x6oWeKi`3P>k1r&TX>7iBrY3{50ze=F5ZC6b4$#EJGw(K!Bpu7 zS0h~z#G>kU>IU?Hey`DLjrazP(Om&0dZ0T(Z%i(A05y1)(IfGYC|8qTeO!-2>^3ueKufz9xZ)bx+6qiCU+0C&ov zEKLuSOX5j<+4u5%>S*U^!?nG&3-tf!tAxx5*%kIREGBGN*xj(=#$Z@m95Fu_!@?(q z7dQ4bRy1xit_=G?9ihE3f*vAG!VZLO4=o;M2<;krHYAL-ILL5c@1plj23XZWZ6D3= zAY2i?;Q_z$TfC=aD*?mRm z6L!`w2eLtm3$VQeI7tD84(u)zF2^mwF8m;_B`!Up%A}J2Qnf?#R`Zv}rMF^LARK7aTw>5bWL5Ym1_C`dNNPa-of!h)?LtKY9h2hYva_v zsU=p>br=o3h#_Xv)4C_I?r8ZX@$)kx=eyDs!3~DiMcl2tt2_=%t`wN>8a%rtIOaZo zC_N|-1eVcb?m<8kTtYwg_VBO1r|)zlykiu>YQe*JbdRD=STQ(0_+AmFOojaLoT^!J;rd8oM$%6T2Gz-;Xo?Oe@A z%_2UYp{L9O?OUxASCQgi?z7=pv?s6rN0p3s$5#0rC$5Po{S@PnK%DsitfL#a{d)Ek z2`$8QxJ)NeQ8QIy_BuA?NQ!EoPTY(Q|aG+Abm;tyW|teyOYNy7yt6< z^UIHGKEC)c^n>Bc{m-4#3a8C94K;=2h&h=#|K+sBbL#|pU^5)OTKEb^fh=9o`n3*Y zr|^?e5z#Y^FO6x2av?v_N2mdQ>2>J>8^lwpEe<{};F9GjMWK?vga2pGV2_OQdKw*b z@|Y)oQjc+pMWoKeP3b5pKWmHYhSBGDuHm(zZ0PLJY3!j+;|SOTkHb$G%Z9g%xEWD6 zd`I|!5Pj%6-4}e>x6mE%2lXQuKGlhH!@&%jf|*{%gH-{e{2Gdyh4glvgl4%HJ_7UI zOWjW($=7pC#d%~UYT;Rq*697qIT9T6@c2{UL{Jy6trB=<9R_o~4OhzyruGZ*mJKYX zD0Qt8hVh1CVPiPk;=xsk45JfG-lkq8e{eMzj`9JzNMH5n z`Yl2qc`UL?lp1f~ABUS_=b^m5_LJ?WFvr+-ZNcon_#!&!^7 zy1?i4E($N&;MnVENKG*c&)a&cDXPC&sV4IjbE=`UajNFLrUN*BJX*P{=?M4<-sOB{ z4?I)1U}mP@21V_~+RxTB(Eg%3r<-ibG9C7x97Ez^#EzgX%G_{`C#;OSBxYtT)<>c2xhbXo9sH$r4?3)kyAIQm(y}BgPhO zyG)IFFB;MFzt8%96>aTpxyy3P<^<&wD}m#ht2Orql?DC z;!Sm`TpuL`_H?53MY2hK@t)M9HSi4OuglIXXEpqsHI7)ic^5iL)8}3T4T;;R^M7&0 zy6;E@QZ;&Z{9yo|Cr9|A8>aumkYfn5jI#Km`}8s(F|elfmUUz?uh5rav%*`^<6wzg z9N8{BGh7vMJmROY$6{ z9LH_fZ69F5TrYYD>w7#*nm(v@pD4O*)7UrJQfv-~LI-j=G%CW}Pu<_#HYr|SjoxS? zom+o^GsNM=Rtk6bqxilZ#>XKWg|ExLaegHNRt9|c?;LPCuv*ZTfQZ0H_&%rux(6)x z&+{*XuJtwF3VyBdtBUYF=KGJY)$a;^a3|p=E%f>Bqw$&J^RK0XkJ-G-{Lb9LGR^cF zr^8<6#^_sBgnzZk_yb>_mj;pjE>5>gm#QtJYeCN7OWr*JEY*+wK1lpc9FHDaz8tDd zrvJPT+I?-&hAW1DSSb7%v$FzPD9;={?0?xsM~ve%S|EGvPwkU!%WM@ZYxKQ!l|&p4&3@=XhT(w+*Us-|i>to=xQs3WP)#9md&_dU306Jk;iCesY= zsWYM16prd>eKf^F0_lAZNMCAR*kCTYYn=qG7=>rU(brr~E z*66*6)voBO>z5Ou7ip{Nn#0)j0iDpJJJ*D2YPfcq_8__ODUCrJ3iqft6{9&I=v9cN zD}aTk!YiLhRp9_$fA^_chR|oXmOWqq>J?vj%5_xTRXyl%DM8Oh8RFLjJkviau`qY+ z!XA1~E1{*?L|7!~g+_FMT*qm9FdZHtVlb*w1E|tlVer>dB~qKNjR)}%-a8Xk;8oRf zsy9CL#Eby{zX|pm0t!_CBApCwwE@g>FLkY!)IF`#^RB^4j-uZ;f}W%vnl+kG?HC+y zH)uO*kK(>-)OFG>)gI9X>q_zSP<*3jYM&D=X?3!uBO~!j+ZnAtwYC{Vh+*1!K zWyI0=ZjMtWakm4--QqlSG>kYQ7NI)&Cq2BY=qs8)@5>F*F0R01emdT9ujuiJrf;G& zy~1Tx{Zv+ZEt-QTH9&h}fNBN()5p|BXrX3-4_u&bRG#`+8a(+D)W*ELYj_{v+7gIy zOEQllsadFCl7I0I#y?`Kb~w+vEd20QkBv?JK!eXn3J>?E(Ippy5=f4lPE{ndx)Cb3et z>Z<;Z_UcR6H#_L8jRN2IB}#vXhR^)$4&Vda8MC$;U#x$;d!Xd*kPp>uvY0sQC@7v^^R1 zeQ@)O@G4(|m3O1pD-Cqm3Pv0PhVH@ZcY*sJ<^KJsxmoB`ThMx!y&p2UNtP)sM;2u8|9m#qp99~YLSVMK2 zzL#KlC#TS4E1_zs>OnvKES$e5!Pwb>yWk%5aVIlYXH<7p2XT#iDC+0{d7_#|&;0<- zp%Hw40cZS>N=tXhY@S7XSYk`nBf%qH&=(WKSQ+RU+Qr!1REJO%dEvEz(TSy6R@{4$ z_X_@d0QmMS#&0{7ok!rT>%jfvKtY;pz6MAjQAMr@r~*q#%zM^z#-sfZoMLao%BKI{7buf1OYuiniyj$j?d zGDb;^OL^|0I(40VulCf$HdCpr?=3RhUsJR3%@M6b<~TM3sXlSd7~8Lv^56Piob@sRzMGRY!o1&xJL(2Uo%h?6f9&n!Dlh*hlrZ zDiR*eQ|5gjPf<(Px{AH!9l7yd`hSk#>|T;Cuo2>RxSG{Cb@BIti!u}?fKKS0ugyM^ zJ?>qvcMa3U^zNBanduovGM1J4~CAUZ}~rRq)@|b!+9zlE!mrF zjtP*CJ!g-2X(T&++W97$bwCeD%v&C!;%aH-q- z-}LX{f6c$A-$TCzK|$67!NWuTvvvu->OUypf=?Y^i%*hIJU%Tm!D<|G6J@{awd;xf z9TEK+dkGZr^yvBbu`ROQM<1sQ_FZJ{mF)zIHC*-Y8y+Lp$?+kKalEAO%zf_vcWw>zkuwTk~X z|D~32pULJAW)-}{gYYm(J5e5M%bP0lSSoWv;8wYaL8};s;{+6JjL{9n`FWtOEVY7EO*!pa*1;6^ zwj_F$JdQv$v{SU#!9d!vNBjgf6s{Sism&UgMn&fw6`PGzg;Vf?eogmNp}GQ`mu2cr z)MXCRgZZy!jYBQZEWOMR%?sg*C!0T- zwLVQjiJSUXv23-BX3n3pn0?F@CRJA zZOpqWbj!Mxa#SEvgciyQB_6$xH%chn&sWL^AzXARQG$_9TDw?J6+*A#GwSLQ-oL;+ z=tEc9FPcGU5_h5=5oqXYsErrYb$z(OgS(~ZRQlt(eBF1Vrp-j3<)~E_XOF*6O&zsu zb&~2fIM_z9jM!c5K^AaDQ47aV^j=S1R2!w3t@u-~l2dRq%8&=a?j44Qd^&JpAwALYtQNwAy7a9CTxZ><6+@d5YN z7G;o=%%{d^e&|&Lh!}l1-%hB5)dOMubnqGhx2TA{*#t)-0iMfSbrZB?>Uqso=fLB9 zrFy1TdBwuNAE~;8{=9~+$)=pQcJY+pLQB<$_^qFKi5}+DaA}Xg`unU@MG>sNFqNL) z#&iqkDBi*&a-}2W0VR0$|KM@Ei9Yu3D8*Jo=X}50=61QuOYc!%9E-;O&*(1t;1rU8 zXGA4b!s@$6xF@0Q{tB0otIkyC0JO8lxYoIL;9c>_T~Qh+okj_D8))V@X&}AEk`y6t zQ*1bB98}a|3-LXQv2{>|Er7GQ1wOce{@sPD3#!SSSy}2vR39pPXQ^9*IB%uOdBtlD zG1?vYMSpU?cU5M3c=r-fIK)^qd>l$Ep&yFs7iAeO=Xvr=d>u_ntkNANAT5Y{Jbe5l z`M5j=-P(ij*l)07K46BmB39ic$Y|M}XFjHYs8u5C9x2K~OK~_mqMA9N6{3U&;P}^M zi?T&}kFVn)S#md)&Z7@sK%HVjDwA{jEUqxU-dl=1L^Uz;ww`N zl%gUW6;V@H^TRuV9HFnaCHvfOhRud(Jj5zcJNn78%ks>xhJTNM+W{x(q1Z%+Nq#_& zKriby>*=64YbEO(>!LtaP=ER?X81j$_hO8{(QmHbB>!~(u7URhuToW8I`j^PBk(1DyP{kdNYZR-COOEXrw>GwETyF8TCGN#7 zEq*DcSM1}6Ns;@*6Cy-VM!8sW=xgiH;BNr~0wdA#o8b2Wyljw9I=bjJ$ZHefFLZ$a zy&kWISX*;j)BGm+Gjo>YtfL<>>`UO6j+xfX66t5(8q>DEsrdGL+WoYow1Me?>60@` zWTa;Pm-+eA;ZJkF&igtEU*FIlem~9^Ju1p}I33+sqw9&CeCcs`NG=deUQmNhRZp$F z3$v)2YOrcGSbBGm)RJ-?StD7bSLk^@W~QG-D?lgv%6CzR>H!X-r-$W*-p6pl9A`P} zm+jZtdfi$SGPzjgh}{vJBYQ=?j9C&pC$4_+--=f*ai{p)5~bqq#|g1(VuwV^k*=_R z!~Y7YT5M-fC2LH;#(<^NvOC~;H${J2ABXNoH6pa8@cTTN|0sCNg-T^%FT0FI_LJw* zO|gXDibbwt=mE$6AHUg%Ui5P3M(3Z7xoC1kIX8fI%|;*hq;0$HXZpNW+RpO5x2>@~ z!QKts$7RG8<84dPifdzUV*k}%8BI_hht)C8KF7YtanA7zudU~(={SeO&?EbI`!z=n z>RU$~|4OJI;7ddHvBS*KGhnRo29JAD3ur><57@Qe2z}l1kuswmnK>>k(2QK#e=6A}crSBlT_M)lh&opKjWnFXq z0s5NilCMUCCLM?8x<+$Lvw^I(1GT3|RA^pnjIitnaGEFS*6E(>I_hTY(%}%iq^EWU ze>+)oT~n28wgxe0Lzv9BaqsXbcN{_Qwy~>*>#6;P{g(ZpJs)4fnfT0{EBd$S3mSoy z&>zd?dnfeAZlXQh3yk%aBh_&k4csm4Uq8G1y01~si=>7*guP^vJX_9Vzi0-BydORK z%js*_ORPQz9%~sQd{0le4n!u;yMbm1bvHp9N`Ju#_yl>{ zSlxX%40E*?r~nt&jo0qdPS#z}dGgb5Ou}nrv@0IH!}jhm?i%ja z?Bva*EAG3fAC|(WcAS*OdU;K~=o}GR6#Y>3-QC=y=>h7Dw-!+%k-#DJa6?^f+&{a^ zy1Jq%+>rO_1o~J-N|gV=>90sWf?~H#u?W6Y{U&lQuBNxP8~g4R@hx5pf;s>d1S?(s z%ivF_!Sc(IdvD>aE{?uJDtW_H@Cub!iiqN-=psHCPd{27=iW>-{#OwLR{@*x;4`Js zEGkhV(e3U@UH%BYT~*P!4CJnQyV|%!_ea+i@ZzKBvp)klj%K`S@Y_G! zGu;R1?b_}>>E4H)+#B~tw?-~5x8-!JE_aiCr4lI5)se2dzc3EHQBA&y_tIAMg|E36 zpfCIr{fqr@KRC3v+ zU;x_~^V)E{{m@`I!&!R*f19%4qRn~5T-8&QFb1oVssBdNKk$@11wLR`Pmx>$7Al(?*Gtc`vI<&f@WGRv8~t;=294lW;V6!zF=L`HCHt?b^CS6L~kxr z9rJj4*wz}p7{-E&_BX9I{YnQ{HTH&)uo#oLkLh$-yi=F*N)j#$KZEgJ7cvC9%3ocX z46-*m?wxVt^6(9N5$o30{H@sw*Rd7C-g6HA7UQsEkcOVu68T*aY*XM#e zoCmdv)cvO02hYcgexj1pZ5!$b>dV9E80ygjU?0pvbHEA~=HVZ^ylQ$cMz7+8m*S-& z_xAK^Y(OV&k+>7BxLc|V=&PiHx1FO4s~U=LAw-iU;Tq`pew{hA3SM3uIw0T4i^jwI z?9Wxi!<&&sJ@{5L)is{$F*q?c6wRkeTck(0k`=lmrSei!X_G6(b<-_K2hbVcK(C(~ zHkBT2@(J!_cLyTBt;D}i-D-Bz2zev*i{Z*bNqS&B>-q6_xMbC?L;f{vcNCd84y zQA5xR#e^zKa}e)_boT8-!EL$_Nw1NM9QzVcV=H->oT=!9^5n_ol~&4LdSj>KGWUuv z8y%5#;1m5#-rj^OHM5I^QiB@KKGMQ9n9k7_uEl8ImSpbrYAdrNPvs zPr>&YgtvWf@`w4-ZK)AnM+3nqH0;0%bx2P>HJ-jm3**+9p1(&z46F~mY99S6ZHeJ-NwrFVDom<%VP-57;H&kgM^m+fxA>g^phg{h7&dzMf0@ z(gP_+n#O(nLEbVRPq15Pj5?$cxf}Q3>EYZZA0WD~s`Q~3ZwtDUW0)76l{rcRWAp^n z^A53ob9PWGJrjqi*PJGP*{pk{>qGBk3&S`=1dcm1^jq|aR5^SMoei0K13CE?{T#zN zLs$KBx-1jP`!DK-8g?7(@H@Wg;|)L4i`WjP(p9=yXB%G_&E|3DeWnoeb+gqn)pXC) z%CsG;v*MUM^ScuYJPIwyu6g`lP>d%#cFyR|s9t?SFX|M%h+o0% zOmI$v!4e9T`qbqXDW~u<>CcMvXmI(WuXUOjVLv%%N7g`NR#>iEagV_3@~k@@AA%NA z6fsR@_D8#9%|_rJuzThX06Bt4bXjL8D&2q*FlGWtc*B`IC%;BLnrktiu-@k!J~ zA4u@EAW-&S@zk|!`Rgo&0GjT%trzfU8 zqsgMvKb1&yn#KYelS(Z67=F7iy%e`u6PI8PHUbf@MvYA2$&6=@=ms*-AOFhIQa7m~ zYw;5}L^WvudJnBx&k1;xX1J5&tyFb=l^4QuVz~feF}>9V?8<7~UGl*WcHlo9#va`O zJu;(GUFm=hV`cnYeiDWW^{D!;mLG%NJc8GINeB`eQ^!o<#pTs0Ft=B6#(txAQXDSi zTk!{+#J#Bd)=`=8S{saSjF~;FfD`FoG!PSTC_Rogekb(Rezu9eCLBa`*-Gdp9HuX`7xDNw?G|kzHOtEIXE*3C>u;igkcX~BGwQ@$s5*=zes!U7 z5v6og^zLx?edjA@6kaD|(M*1pe>XoQ-}&Qg{`{HCRT44jQ0)M}G8so=ofsjcr3Q>nTy5N{AKZNZn);Er;a!aw0K9zY8m z2XVRl*YTFM+rmAH6Kah6CFr&bhrx-s1O0{0P%8V-Z84LYYd_U_@VY#;hOUD3UL&bV z8qiq2?e)X!jP{*Ypv$kCdH{TXqo`H2R#gGJ-y`3YH!^M!L>rUPvgnDYYen$-VvgdD z-|-*$%e4nim5DX6fiwSsvXcCu3R=K7;M=W-7v$l5ccT}6gW*5Uo-&5h>|u$fwWdn2 zGs27mjQ++x#_q<|#-H`;KvfeAui+q?ILQX;kL!nlyk9it8l$P;o3)AB{=~%7(9fE| zp5X)fa)unn8y(v1Xnw`e|Dh>_h^{D!AEGWZH4WQ{h9p}Sax%FRXo^2T^a7L|MzElz**2iZXs8J1#yfsKt|`$ zGshqEES|t=JqbsozuZGEMV@z%*motn>924^FA?({1QC2e1bIWg4<>v_DTeZ5F*G-q zDeFBd(aJ93(UNFFR|D((Otd+Ryr&IX*8`P5m7VA)_Qz9dCGV0lh;G$jP@hb6hSu5svbD2Uu@~B|*akQT;2iVT-pSblq~n>h9KJe#fi!J*?{I5D z{ukh-TnhZ_5hp?@{zL8Mu0%b1)9J>k+pvovV>z| zuDBlc?czd+Fa`{Kg(r??PaY0_k|W#XS>V7Ld@1HS=i~aY$f?4+W|*Thu2kNRZMG%0 zXZS;RbkuP?cRqHu$EV80b-uDcu$vuO_9F7}r_NK(m(DA&VtYD&a}9FMM2poMpS5!A z$qBIKB!>}S>XY^i#}z!UIy;6t$2o(X2Inx&m?Zp>3f;ldJ0j0C{JRo_*1{V6&8Bdb zcX`g8&{1SKMveq8e&));56R(-!Ns8l-dD5m-Kvb=j%>F&%EN1P+P>mQamO|Y z=ZN~w^3KO(_62xFjE9{S%k#elk7k-MAJ5f0N;qfL46-MI{*vP0C|`Kr{;bBAIQ=wr zG;{1NI$U%PKfJE^H=JfiiYSaLR2OIqz7|}^DX&q%pM{I?=DA*YplB!hu4nAqo$H;e z!3N8?qunEkuYXl$DwWY0&m|x5C_;RN6ZI5bgU!NGbkiDf{_T)sIqMsO#f-q4N$aqn zmwLs%%eDsBtYfyDMX!sF*v{aiceW@FFTk#NS^Vob?Kq7GnYUz;?$HsGEZ!CKs1yFq zKGdG>q{{e9{z6PA63-sgIdv`QH)x|v)}7G#=)KXvYYKYxjW}T?`FR`7Y|V9Q@2_C7 ze?j&2Hk^fzYJ=A{ddnBnzxITDyBIm{Dp0RvG@^D<)0$xZY#wBOV18<J@NQ%l%mH^z;N(Z{Zh=^gt4zH8^`kI}s&rO5dap^>FRwuh`od+CF@jAf2~zkV)U zfyd+lHoMVL4+j=gR*9^#nE{#pZ%(G}d0zJU;Kwf?t$Gyw$o8<$!#PjBJzn_K>uJP` zJHjHt-)-q zNBc-N_p3o)-8$ za0YeAYo!t;-pjbL-ohZ_cmBzK;Cd<#VmifuGBK z^#0i9Q?XAuA5VRBf4ao>dH>6A_+&V=f8YAo8OoVU@Q&JAOPsT2xFDxcauV|*_AO!Ry1m*v;Rzqx;k ze=q-+{zY1tM0rvvB4N$CRk zz(SCo+45I%dLwGOI;w2t(S)svzRGeKC?~+wa;Voo0)soQ_0<*X>gzYau^5Ez{t;cC zuCxB2K2rBL^|0Q$1o~R$(Tf@nTPKE`?Hv4pYH%E{2v3CJaAjALH`M0S&VfgLBOhvs zcbg1H))S}y4F2-T`PzAgb7GXUk8>k@k%rEi&Y6y19ltxCemMUP_@I zu@1f2J+QXoV6z?7+|*>S2EM_x2?WQQERU0alSW8wiR+$_ha9CYRN(mN=!^TnAJl#> z6+J0hSh%*ZcA>FQqAvCrE=vRY-99yn^Y?a#&48MY_0KK_u@U7hr0qyO}b}!E5FZ}z5{TnCsH(Xpwpl+Cs z6J(mr<;Zv3L?1oNo#UQ_Pp-!ocQG!9ov9V1;VQnJPP|!ge11Yp&VasnU2?M+C5qml zKKNy4Di4KsuvE(lo$0G=EW``*sOvW8iPn(}yLaMx?@nnK@z$}PLrN}Eu>e$8sEMin|m&ZW1Y6tR~|HWQl!DwEi$tmHc( z`1KxfUvhs#?=2l2I3MEo7I0k#aIyz*;?9GUUYxlf;I^Xp*37*F4*witi$!4m8=3J_ zz$a6|@>WX6q~TPJYSH^t1D}XET+`xNBiE@5x0FZ8t?(Vqk&W~}ZB=HI(>@_S`j3At zV_ZJtJz|yp;b^~;pMa;Gg-1~Zf8jnLh-JXJ>fyKU(WhUFqIo_wm=e@hhPub&1RDiP znm~;uM!H69dsqI#h$WC!S5Y!?g1tl>J5ZiVZ(<4gfN~Witbu|;G* zXTHvQ<)S@N9G=~9?~_#UAA(M=rH^hadatcDziBRc7kS?TX}(L}eo1s$LgkZrWZv795{KWvok2O&gT9AU2#g6H6THK!3ob`({W}=YS%b=0j?Q*FBbp?ymCwT0 znoFG8##zc4;WoQX%$ydEzK(|Wc>5~*GJ7KW_Cx4n+p9mLH=3)MJNvHmjrX7C-!X7o z;4!@KPFscG?!}UdwF>zsWI@P>kavOAgCxJE{u(+C4(aac#?#FiM7?JZ8AoeZu&a$D z(6Oy>LSc8jm^43f^FHMs&t0C=F{f@;{EWSbKXJE?Xcj0 zSN}-T$|2-camv|p_iA(S~CZpZF zPk&Z_*_dTKgl3_O-jNu%3=Qd?_+lz&R+$@_+u%ulS^q&lo9OZ=-T1$$W~x4;vg$%( z@f9u%4IJ@~Mf7s7!0YWP9@RcSzU6)Yk^W<8UeCPGKQ8^4n?FAP-+~zh`l9!R*NUzc zEq6?CJf-hu1HBRZ>7lqtt=$FMkicoBkRR`pHiJ}mmDQ3j`$eE!9gpNrU{(95XL#Wq z`rfUPmQvklOP^LV_W-vSfAhOM64lo#u58^J%Q3Aer>HPJ6%W-Y)z z4zh#PX9qE}5BjstXW`$P?Fx0@#l!Xv-lPj$7wH2rx^}xxx+HvBm$28@B%9oeE@c-~ zJU#VGgB&6s1P`nTOGYb&@V%Ve7p(jtILrn2TlX9MpcjKih}>%pu%J2cdxp}D=X}qRB zYItijeTnI!(5CVD0rjH?y$4*UfAEXSfCc2~109O1O>bqQqJyDSk2-;h>V}^Zpqv56 zFDr*~?hTMCGYbB)71s>`|6Hql&wbHdK&Mz|@-Lg@k~ZS^b&-147%(Xv3 zNV{{tP!gqScq~l92lEl}WLT!k3Cjg4~4@#7AiX&e@t8R08MBQNEs2-?>DE-3$`3ma`^;?&Q*P9Q`ln7Z zIx5VUz%hc*UVKjXL?u-T5UA;@WALYTs7|R~@FY|I&-I=!9;Sa+R7I<()UB9YW z1-{k=xW1=^lJuzP@WcwlJuFNt4Wr^5qZSDtZlACS6zyMXK$pQ!zR;nvovw?IaJ4+R zz6AF!39d(7y6QJjefnJ(j_!iM-8NC2=#pl_ORdORYv6Q02G&)9^|^%c*dhHRb&*GK zm(4+h(>Mvo$Q|%5cfdukf))M)Ut+$pn0!4-D95`!^VE|0tlMY@W|CW{Qx!YH7{rR- zgxz$1Zxkj8KKR1r@jTDMCprYHv4+@4>h{cX=N_mI5y63jSweH zqSn0`O>&1+h`0NE*#^d&O?>mOe3c&j#c=JWfT#LX2`Mdm=IsZ@q>0j9xy%01mHD=f z*OpZ3!c%)D&lA>wM>SCfF(2dj%0YE=Gu_QWE~JCRj>1q-<1FFfz9BjJgRmgxhF&4nk}BEO_+= z=DQV!Mt$aA6~0f08`%>a|2VVz9eqb9sYS*U58hMGbJqiS#{C5iPwKXE5q`lnIZ~0y zW^TxXiExInZn|-fPG(JBk}pydo=qLIDvs?oJmD58{Hg3_?u78~gIsej-Z78!c@Y2g zCp}<6up&1rAMm+*Wl^VNg6 zbr=1FH#H~iIfO|*)HJWz#w47nKdV1&s zgyO7`^U6>h3}1pfbp~Dc~tN zsh~M{cL`US+4z>ZsoJy#i>k{0G>rS$M_s!H&ohs|?8)qjz^zUbg|Cw9_$YatE(Ovd`6Q7<96Le{GXBBx zdN}(g_)SasS1q2tz;kL1+arTWLoI7zV};AB;9>3O-6wFm9z;DO1>QhkxsPm@o=bLW zwP|oilBnsmU`4l+o5~}>X(#ifzbjvquk70^nYT~bcgnIuK4+gCDR*RUKTtk%cJ2Td zeM-%=A>((OdH+NupT!OdcA!3d^*@Mwk;aOJ#$G$C} zlfUrlZ(K*N@{RS;l#y-AE;0+w_Y3sBA1Z1xxq7U@7-sSkWdc8cDer_6FjDEt$_rz6 zoT>c5J=~|4aw$3>bs6(cJflX8?F6p37i;_&JJ4aqp+498kY|>}r)-y3$hTNm-FcTd z_TIi+)iAEVke#d7&j5PyM9jgS*~HmkvAHgoNNG2;$XyC}wd+Qv0q=w^Bb%wwupTijUfm`yPD5pBR-XCB;I~bV*%+cHUxv7l_tWU z#0r0)B>aHAH<6wGD>c8Bd}cpllz62U3NODhQxdqsXG&kzb8p6}1u<=q5<#Wm3Zv3c zY0detg8Z^S<28(^FP(c!X2ztlA9(i7+44m4u;c7CSLH2?Qg_zDSJvtTc9?Mbn8P@m zT-2}TasPYSv+6OoJkO&K>!Ad@coQX!GFz#`K0gh9 zjgh@SiRTl*9Tww^`O4|Bf=FdLG0`?5Sx5twxlb(inq9OHCtD+W2oAIIdJwI*<}Sz4 z&(Mtf@kQswj}Bwcd;G;2(S>JShjTKSYyX>{WWX1i#JN(E=&e2I&pyu1$DB@Y;c0}T z2c84(p&K#Nb7C$#XXu|ikxqEc|Hs#Jy|d2b0_6EZC~Xo=_SeXi&naa$tuA{wufC~7%F={&E*dLM;{*HJi9H@J#)-m|*U0teeY!W70h z1;y#!TBJxyEpYkjjAANzc?05`_1r}QPhlk3+&TPt z)wsD;5d*~mj8`x!+kV7%Z+XZ2e9a)%c}JbP60c}STx^gD&_huDL#IxDXzw^A-!6JV}y}5)~OuRr>nanu((}BK=XSH5jEKcKIR>R`#%kwG_yu>}s!}?+c zaV6`hGAk&G*IBvuc+ro%IRxdudT1fMP^Pl3ykS;*GovaB(Zp+W*tN8*r}xS{IMM?D zl>}Ge3g^gsB^9nrXV!b6l8uJX8TPV4!a7bxZ^p>uRp)UzY=9%+c%e1(^*+7-%^0_- z^t?ag3|+|gPR#prjJ8<_XUe`-Vgd3Mf2Th2m6^{v5>HP9Rm;NW7 z$x&W+LAcLa)QVwZRdNpx2Hu^o=gh_Vtcm7m>Xj4A!j01NdaI}xim1__W%Wg{hQBGh zVI)2zL)ytRYQPC`h6+nPcDpp5*A4ul?ogW#XMfnu`M#Pte}$iUi)p;R5e$)ESsBxb z^0oixVD;n9@|itH8N({9jZS>F=VwhAf!B=FA+D!D{wlX;9mH`yHep;|{y%enrMOU& zpT;=<&Tdi`b%i8Yk{-@kWyYi#>-RHf!!BO`4c6&I_Jnzys0$e9tL&jxc1svc?4Q+? z3gpKr>=m7ugB>|B>+o#Gu*&AL7M(Cq4{}!~v6Oh9n)OZY`5C!bWp<2X%;`(~?lgEr zE26=_nRz0Nr@iFq&Di6ba=%N-={7T`qT$5;%Q$(9ZnDrA;cr%aTYkTe$haJQ`3S{I zE-?l?_zZLY4ELDL+1Zg(?4=S-M$(L@Qk#fmGCRpSVx_ZWYkACl3(v0&Bf6hh{|7tQ zCterFGke3ls!o@e2JLyz9$v`q_JQY9mbLeeS$c{&(3LssIXSioCmF3K;36M{h4A!d z!7(k)?(#SDD~Gu5730+aRr+bLLwlf0AHkJ-=Ba_1x&{vVY4*ol-uub_&yn`Raj?t% z%%$1*AT&m|U>`HCDtqcoa-5IMs7N?&{_HK&c>iy3QP;8__px{DaO?JXD>q{9mORX!j+(kI1&Al;oRAG_VfGP@jX0M|K(Y%Vqf^k)2_vSH5r`fgs=nMjYP1e z-SnQ+5N~il9?T(~akYp=jBO(b)36gt1r9n2IkIU?tc~Ellk3N z_KzN7g1CV_E=rWC#W|U^&4nrAOtG`jR~XCuXwJNgVb51sk%E}Py;k9MM>%s!i{se4 zMpBn~$-k$tj^D9=w`A|>$Tfap9X#PRr+5}d)X#QuJ}02-^N^G4nDB;CJ3=PBke%!d zx_NVy6Fj@a)ONP;o0g1aG&}YeVy&Lcq+N{HL!S5%#&Ium%Ec*BKx}vooMjvH=ri8z z+t?|Z@Q$%O-5NZDXWVlES?N+v!#dp4NMQ~$A(x1yCeNfLzU;%?OZ@&#Uc%Zg#yd=99Vx7hOs-QD@|32m#_5cE7xtb!*1-$n%Ru(U{{c$u B7J>i( diff --git a/examples/FtcRobotController/src/main/res/values/dimens.xml b/examples/FtcRobotController/src/main/res/values/dimens.xml deleted file mode 100644 index 63f1bab..0000000 --- a/examples/FtcRobotController/src/main/res/values/dimens.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - - 16dp - 5dp - - \ No newline at end of file diff --git a/examples/FtcRobotController/src/main/res/values/strings.xml b/examples/FtcRobotController/src/main/res/values/strings.xml deleted file mode 100644 index 6ea191e..0000000 --- a/examples/FtcRobotController/src/main/res/values/strings.xml +++ /dev/null @@ -1,72 +0,0 @@ - - - - - - - FTC Robot Controller - - - Self Inspect - Program & Manage - Blocks - Settings - Restart Robot - Configure Robot - About - Exit - - - Configuration Complete - Restarting Robot - The Robot Controller must be fully up and running before entering Program and Manage Mode. - - - - @style/AppThemeRedRC - @style/AppThemeGreenRC - @style/AppThemeBlueRC - @style/AppThemePurpleRC - @style/AppThemeOrangeRC - @style/AppThemeTealRC - - - pref_ftc_season_year_of_current_rc_new - - @string/packageNameRobotController - - diff --git a/examples/FtcRobotController/src/main/res/values/styles.xml b/examples/FtcRobotController/src/main/res/values/styles.xml deleted file mode 100644 index 07689c0..0000000 --- a/examples/FtcRobotController/src/main/res/values/styles.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/examples/FtcRobotController/src/main/res/xml/app_settings.xml b/examples/FtcRobotController/src/main/res/xml/app_settings.xml deleted file mode 100644 index 58d3aa9..0000000 --- a/examples/FtcRobotController/src/main/res/xml/app_settings.xml +++ /dev/null @@ -1,93 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/examples/FtcRobotController/src/main/res/xml/device_filter.xml b/examples/FtcRobotController/src/main/res/xml/device_filter.xml deleted file mode 100644 index 7b75350..0000000 --- a/examples/FtcRobotController/src/main/res/xml/device_filter.xml +++ /dev/null @@ -1,49 +0,0 @@ - - - - - - - - - - - - - - diff --git a/examples/TeamCode/build.gradle b/examples/TeamCode/build.gradle deleted file mode 100644 index 87d4ea2..0000000 --- a/examples/TeamCode/build.gradle +++ /dev/null @@ -1,50 +0,0 @@ -// -// build.gradle in TeamCode -// -// Most of the definitions for building your module reside in a common, shared -// file 'build.common.gradle'. Being factored in this way makes it easier to -// integrate updates to the FTC into your code. If you really need to customize -// the build definitions, you can place those customizations in this file, but -// please think carefully as to whether such customizations are really necessary -// before doing so. - - -// Custom definitions may go here - -// Include common definitions from above. -apply from: '../build.common.gradle' -apply from: '../build.dependencies.gradle' -apply plugin: 'org.jetbrains.kotlin.android' - -android { - namespace = 'org.firstinspires.ftc.teamcode' - - defaultConfig { - compileSdk = 35 - minSdk = 24 - } - - packagingOptions { - jniLibs.useLegacyPackaging true - } - - compileOptions { - sourceCompatibility JavaVersion.VERSION_11 - targetCompatibility JavaVersion.VERSION_11 - } - kotlinOptions { - jvmTarget = '11' - } -} - -repositories { - maven { - url "https://repo.dairy.foundation/releases" - } -} - -dependencies { - implementation project(':FtcRobotController') - implementation project(':FullPanels') - implementation project(':ExamplePlugin') -} diff --git a/examples/TeamCode/lib/OpModeAnnotationProcessor.jar b/examples/TeamCode/lib/OpModeAnnotationProcessor.jar deleted file mode 100644 index 4825cc36d9606badc3c0eac0f61079f9c5a4d196..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6032 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 diff --git a/examples/build.common.gradle b/examples/build.common.gradle deleted file mode 100644 index 78f29e8..0000000 --- a/examples/build.common.gradle +++ /dev/null @@ -1,117 +0,0 @@ -/** - * build.common.gradle - * - * Try to avoid editing this file, as it may be updated from time to time as the FTC SDK - * evolves. Rather, if it is necessary to customize the build process, do those edits in - * the build.gradle file in TeamCode. - * - * This file contains the necessary content of the 'build.gradle' files for robot controller - * applications built using the FTC SDK. Each individual 'build.gradle' in those applications - * can simply contain the one line: - * - * apply from: '../build.common.gradle' - * - * which will pick up this file here. This approach allows makes it easier to integrate - * updates to the FTC SDK into your code. - */ - -import java.util.regex.Pattern - -apply plugin: 'com.android.application' - -android { - - compileSdkVersion 30 - - signingConfigs { - release { - def apkStoreFile = System.getenv("APK_SIGNING_STORE_FILE") - if (apkStoreFile != null) { - keyAlias System.getenv("APK_SIGNING_KEY_ALIAS") - keyPassword System.getenv("APK_SIGNING_KEY_PASSWORD") - storeFile file(System.getenv("APK_SIGNING_STORE_FILE")) - storePassword System.getenv("APK_SIGNING_STORE_PASSWORD") - } else { - keyAlias 'androiddebugkey' - keyPassword 'android' - storeFile rootProject.file('libs/ftc.debug.keystore') - storePassword 'android' - } - } - - debug { - keyAlias 'androiddebugkey' - keyPassword 'android' - storeFile rootProject.file('libs/ftc.debug.keystore') - storePassword 'android' - } - } - - defaultConfig { - signingConfig signingConfigs.debug - applicationId 'com.qualcomm.ftcrobotcontroller' - minSdkVersion 25 - //noinspection ExpiredTargetSdkVersion - targetSdkVersion 28 - - /** - * We keep the versionCode and versionName of robot controller applications in sync with - * the master information published in the AndroidManifest.xml file of the FtcRobotController - * module. This helps avoid confusion that might arise from having multiple versions of - * a robot controller app simultaneously installed on a robot controller device. - * - * We accomplish this with the help of a funky little Groovy script that maintains that - * correspondence automatically. - * - * @see Configure Your Build - * @see Versioning Your App - */ - def manifestFile = project(':FtcRobotController').file('src/main/AndroidManifest.xml'); - def manifestText = manifestFile.getText() - // - def vCodePattern = Pattern.compile("versionCode=\"(\\d+(\\.\\d+)*)\"") - def matcher = vCodePattern.matcher(manifestText) - matcher.find() - def vCode = Integer.parseInt(matcher.group(1)) - // - def vNamePattern = Pattern.compile("versionName=\"(.*)\"") - matcher = vNamePattern.matcher(manifestText); - matcher.find() - def vName = matcher.group(1) - // - versionCode vCode - versionName vName - } - - // http://google.github.io/android-gradle-dsl/current/com.android.build.gradle.internal.dsl.BuildType.html - buildTypes { - release { - signingConfig signingConfigs.release - - ndk { - abiFilters "armeabi-v7a", "arm64-v8a" - } - } - debug { - debuggable true - jniDebuggable true - ndk { - abiFilters "armeabi-v7a", "arm64-v8a" - } - } - } - - compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 - } - - packagingOptions { - pickFirst '**/*.so' - } - ndkVersion '21.3.6528147' -} - -repositories { -} - diff --git a/examples/build.dependencies.gradle b/examples/build.dependencies.gradle deleted file mode 100644 index 1c07e8c..0000000 --- a/examples/build.dependencies.gradle +++ /dev/null @@ -1,17 +0,0 @@ -repositories { - mavenCentral() - google() // Needed for androidx -} - -dependencies { - implementation 'org.firstinspires.ftc:Inspection:11.0.0' - implementation 'org.firstinspires.ftc:Blocks:11.0.0' - implementation 'org.firstinspires.ftc:RobotCore:11.0.0' - implementation 'org.firstinspires.ftc:RobotServer:11.0.0' - implementation 'org.firstinspires.ftc:OnBotJava:11.0.0' - implementation 'org.firstinspires.ftc:Hardware:11.0.0' - implementation 'org.firstinspires.ftc:FtcCommon:11.0.0' - implementation 'org.firstinspires.ftc:Vision:11.0.0' - implementation 'androidx.appcompat:appcompat:1.2.0' -} - diff --git a/examples/build.gradle.kts b/examples/build.gradle.kts index 44ecb6e..dc62228 100644 --- a/examples/build.gradle.kts +++ b/examples/build.gradle.kts @@ -1,33 +1,11 @@ -/** - * Top-level build file for ftc_app project. - * - * It is extraordinarily rare that you will ever need to edit this file. - */ - -buildscript { - val kotlin_version by extra("2.1.20") - - repositories { - mavenCentral() - google() - } - - dependencies { - // Note for FTC Teams: Do not modify this yourself. - classpath("com.android.tools.build:gradle:8.7.0") - classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version") - } +plugins { + id("dev.frozenmilk.teamcode") version "11.1.0-1.1.1" } -// This is now required because aapt2 has to be downloaded from the -// google() repository beginning with version 3.2 of the Android Gradle Plugin -allprojects { - repositories { - mavenCentral() - google() +ftc { + kotlin() + sdk.TeamCode() + dairy.ftControl { + implementation(fullpanels("")) } } - -repositories { - mavenCentral() -} diff --git a/examples/settings.gradle.kts b/examples/settings.gradle.kts index 903bc08..bff3fad 100644 --- a/examples/settings.gradle.kts +++ b/examples/settings.gradle.kts @@ -1,7 +1,8 @@ pluginManagement { - includeBuild("../library/plugin-svelte-assets") repositories { gradlePluginPortal() + mavenCentral() + google() maven("https://repo.dairy.foundation/releases") } } @@ -29,10 +30,14 @@ val modules = listOf( "CameraStream" ) -modules.forEach { name -> - include(":$name") - project(":$name").projectDir = file("../library/$name") -} +//modules.forEach { name -> +// include(":$name") +// project(":$name").projectDir = file("../library/$name") +//} -include(":FtcRobotController") -include(":TeamCode") \ No newline at end of file +includeBuild("../library") { + dependencySubstitution { + substitute(module("com.bylazar.sloth:fullpanels")).using(project(":FullPanels")) + substitute(module("com.bylazar:fullpanels")).using(project(":FullPanels")) + } +} \ No newline at end of file diff --git a/examples/TeamCode/src/main/AndroidManifest.xml b/examples/src/main/AndroidManifest.xml similarity index 100% rename from examples/TeamCode/src/main/AndroidManifest.xml rename to examples/src/main/AndroidManifest.xml diff --git a/examples/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/ClawConfig.kt b/examples/src/main/java/org/firstinspires/ftc/teamcode/ClawConfig.kt similarity index 100% rename from examples/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/ClawConfig.kt rename to examples/src/main/java/org/firstinspires/ftc/teamcode/ClawConfig.kt diff --git a/examples/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/Config.kt b/examples/src/main/java/org/firstinspires/ftc/teamcode/Config.kt similarity index 100% rename from examples/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/Config.kt rename to examples/src/main/java/org/firstinspires/ftc/teamcode/Config.kt diff --git a/examples/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/Drawing.kt b/examples/src/main/java/org/firstinspires/ftc/teamcode/Drawing.kt similarity index 100% rename from examples/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/Drawing.kt rename to examples/src/main/java/org/firstinspires/ftc/teamcode/Drawing.kt diff --git a/examples/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/TestConfigurables.kt b/examples/src/main/java/org/firstinspires/ftc/teamcode/TestConfigurables.kt similarity index 100% rename from examples/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/TestConfigurables.kt rename to examples/src/main/java/org/firstinspires/ftc/teamcode/TestConfigurables.kt diff --git a/examples/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/TestConfigurablesValues.kt b/examples/src/main/java/org/firstinspires/ftc/teamcode/TestConfigurablesValues.kt similarity index 100% rename from examples/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/TestConfigurablesValues.kt rename to examples/src/main/java/org/firstinspires/ftc/teamcode/TestConfigurablesValues.kt diff --git a/examples/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/TestKotlinObject.kt b/examples/src/main/java/org/firstinspires/ftc/teamcode/TestKotlinObject.kt similarity index 100% rename from examples/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/TestKotlinObject.kt rename to examples/src/main/java/org/firstinspires/ftc/teamcode/TestKotlinObject.kt diff --git a/examples/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/TestTelemetry.kt b/examples/src/main/java/org/firstinspires/ftc/teamcode/TestTelemetry.kt similarity index 100% rename from examples/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/TestTelemetry.kt rename to examples/src/main/java/org/firstinspires/ftc/teamcode/TestTelemetry.kt diff --git a/examples/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/battery/BatteryExample.kt b/examples/src/main/java/org/firstinspires/ftc/teamcode/battery/BatteryExample.kt similarity index 100% rename from examples/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/battery/BatteryExample.kt rename to examples/src/main/java/org/firstinspires/ftc/teamcode/battery/BatteryExample.kt diff --git a/examples/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/camerastream/TestCameraStream.kt b/examples/src/main/java/org/firstinspires/ftc/teamcode/camerastream/TestCameraStream.kt similarity index 100% rename from examples/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/camerastream/TestCameraStream.kt rename to examples/src/main/java/org/firstinspires/ftc/teamcode/camerastream/TestCameraStream.kt diff --git a/examples/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/configurables/ClawConfig.kt b/examples/src/main/java/org/firstinspires/ftc/teamcode/configurables/ClawConfig.kt similarity index 100% rename from examples/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/configurables/ClawConfig.kt rename to examples/src/main/java/org/firstinspires/ftc/teamcode/configurables/ClawConfig.kt diff --git a/examples/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/configurables/JavaRobotConstants.java b/examples/src/main/java/org/firstinspires/ftc/teamcode/configurables/JavaRobotConstants.java similarity index 100% rename from examples/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/configurables/JavaRobotConstants.java rename to examples/src/main/java/org/firstinspires/ftc/teamcode/configurables/JavaRobotConstants.java diff --git a/examples/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/configurables/KotlinRobotConstants.kt b/examples/src/main/java/org/firstinspires/ftc/teamcode/configurables/KotlinRobotConstants.kt similarity index 100% rename from examples/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/configurables/KotlinRobotConstants.kt rename to examples/src/main/java/org/firstinspires/ftc/teamcode/configurables/KotlinRobotConstants.kt diff --git a/examples/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/configurables/SortedConfigurables.java b/examples/src/main/java/org/firstinspires/ftc/teamcode/configurables/SortedConfigurables.java similarity index 100% rename from examples/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/configurables/SortedConfigurables.java rename to examples/src/main/java/org/firstinspires/ftc/teamcode/configurables/SortedConfigurables.java diff --git a/examples/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/configurables/TestJavaClass.java b/examples/src/main/java/org/firstinspires/ftc/teamcode/configurables/TestJavaClass.java similarity index 100% rename from examples/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/configurables/TestJavaClass.java rename to examples/src/main/java/org/firstinspires/ftc/teamcode/configurables/TestJavaClass.java diff --git a/examples/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/configurables/TestKotlinClass.kt b/examples/src/main/java/org/firstinspires/ftc/teamcode/configurables/TestKotlinClass.kt similarity index 100% rename from examples/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/configurables/TestKotlinClass.kt rename to examples/src/main/java/org/firstinspires/ftc/teamcode/configurables/TestKotlinClass.kt diff --git a/examples/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/configurables/TestKotlinObject.kt b/examples/src/main/java/org/firstinspires/ftc/teamcode/configurables/TestKotlinObject.kt similarity index 100% rename from examples/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/configurables/TestKotlinObject.kt rename to examples/src/main/java/org/firstinspires/ftc/teamcode/configurables/TestKotlinObject.kt diff --git a/examples/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/configurables/TestOpMode.kt b/examples/src/main/java/org/firstinspires/ftc/teamcode/configurables/TestOpMode.kt similarity index 100% rename from examples/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/configurables/TestOpMode.kt rename to examples/src/main/java/org/firstinspires/ftc/teamcode/configurables/TestOpMode.kt diff --git a/examples/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/configurables/copysemantics/bad/Example.kt b/examples/src/main/java/org/firstinspires/ftc/teamcode/configurables/copysemantics/bad/Example.kt similarity index 100% rename from examples/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/configurables/copysemantics/bad/Example.kt rename to examples/src/main/java/org/firstinspires/ftc/teamcode/configurables/copysemantics/bad/Example.kt diff --git a/examples/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/configurables/copysemantics/fixed/Example.kt b/examples/src/main/java/org/firstinspires/ftc/teamcode/configurables/copysemantics/fixed/Example.kt similarity index 100% rename from examples/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/configurables/copysemantics/fixed/Example.kt rename to examples/src/main/java/org/firstinspires/ftc/teamcode/configurables/copysemantics/fixed/Example.kt diff --git a/examples/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/configurables/copysemantics/lambda/Example.kt b/examples/src/main/java/org/firstinspires/ftc/teamcode/configurables/copysemantics/lambda/Example.kt similarity index 100% rename from examples/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/configurables/copysemantics/lambda/Example.kt rename to examples/src/main/java/org/firstinspires/ftc/teamcode/configurables/copysemantics/lambda/Example.kt diff --git a/examples/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/field/BezierFollower.kt b/examples/src/main/java/org/firstinspires/ftc/teamcode/field/BezierFollower.kt similarity index 100% rename from examples/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/field/BezierFollower.kt rename to examples/src/main/java/org/firstinspires/ftc/teamcode/field/BezierFollower.kt diff --git a/examples/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/field/CustomPresets.kt b/examples/src/main/java/org/firstinspires/ftc/teamcode/field/CustomPresets.kt similarity index 100% rename from examples/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/field/CustomPresets.kt rename to examples/src/main/java/org/firstinspires/ftc/teamcode/field/CustomPresets.kt diff --git a/examples/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/field/Drawing.kt b/examples/src/main/java/org/firstinspires/ftc/teamcode/field/Drawing.kt similarity index 100% rename from examples/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/field/Drawing.kt rename to examples/src/main/java/org/firstinspires/ftc/teamcode/field/Drawing.kt diff --git a/examples/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/field/TestCoords.kt b/examples/src/main/java/org/firstinspires/ftc/teamcode/field/TestCoords.kt similarity index 100% rename from examples/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/field/TestCoords.kt rename to examples/src/main/java/org/firstinspires/ftc/teamcode/field/TestCoords.kt diff --git a/examples/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/gamepad/TestGamepad.kt b/examples/src/main/java/org/firstinspires/ftc/teamcode/gamepad/TestGamepad.kt similarity index 100% rename from examples/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/gamepad/TestGamepad.kt rename to examples/src/main/java/org/firstinspires/ftc/teamcode/gamepad/TestGamepad.kt diff --git a/examples/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/graph/TestGraph.kt b/examples/src/main/java/org/firstinspires/ftc/teamcode/graph/TestGraph.kt similarity index 100% rename from examples/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/graph/TestGraph.kt rename to examples/src/main/java/org/firstinspires/ftc/teamcode/graph/TestGraph.kt diff --git a/examples/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/lights/TestLights.kt b/examples/src/main/java/org/firstinspires/ftc/teamcode/lights/TestLights.kt similarity index 100% rename from examples/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/lights/TestLights.kt rename to examples/src/main/java/org/firstinspires/ftc/teamcode/lights/TestLights.kt diff --git a/examples/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/opmodecontrol/TimerTest.kt b/examples/src/main/java/org/firstinspires/ftc/teamcode/opmodecontrol/TimerTest.kt similarity index 100% rename from examples/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/opmodecontrol/TimerTest.kt rename to examples/src/main/java/org/firstinspires/ftc/teamcode/opmodecontrol/TimerTest.kt diff --git a/examples/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/readme.md b/examples/src/main/java/org/firstinspires/ftc/teamcode/readme.md similarity index 100% rename from examples/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/readme.md rename to examples/src/main/java/org/firstinspires/ftc/teamcode/readme.md diff --git a/examples/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/telemetry/TestFormatTelemetry.kt b/examples/src/main/java/org/firstinspires/ftc/teamcode/telemetry/TestFormatTelemetry.kt similarity index 100% rename from examples/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/telemetry/TestFormatTelemetry.kt rename to examples/src/main/java/org/firstinspires/ftc/teamcode/telemetry/TestFormatTelemetry.kt diff --git a/examples/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/telemetry/TestJoinedTelemetry.kt b/examples/src/main/java/org/firstinspires/ftc/teamcode/telemetry/TestJoinedTelemetry.kt similarity index 100% rename from examples/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/telemetry/TestJoinedTelemetry.kt rename to examples/src/main/java/org/firstinspires/ftc/teamcode/telemetry/TestJoinedTelemetry.kt diff --git a/examples/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/telemetry/TestTelemetryFTCStyle.kt b/examples/src/main/java/org/firstinspires/ftc/teamcode/telemetry/TestTelemetryFTCStyle.kt similarity index 100% rename from examples/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/telemetry/TestTelemetryFTCStyle.kt rename to examples/src/main/java/org/firstinspires/ftc/teamcode/telemetry/TestTelemetryFTCStyle.kt diff --git a/examples/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/telemetry/TestTelemetryPanelsStyle.kt b/examples/src/main/java/org/firstinspires/ftc/teamcode/telemetry/TestTelemetryPanelsStyle.kt similarity index 100% rename from examples/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/telemetry/TestTelemetryPanelsStyle.kt rename to examples/src/main/java/org/firstinspires/ftc/teamcode/telemetry/TestTelemetryPanelsStyle.kt diff --git a/examples/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/utils/LoopTimerExample.kt b/examples/src/main/java/org/firstinspires/ftc/teamcode/utils/LoopTimerExample.kt similarity index 100% rename from examples/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/utils/LoopTimerExample.kt rename to examples/src/main/java/org/firstinspires/ftc/teamcode/utils/LoopTimerExample.kt diff --git a/examples/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/utils/MovingAverageExample.kt b/examples/src/main/java/org/firstinspires/ftc/teamcode/utils/MovingAverageExample.kt similarity index 100% rename from examples/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/utils/MovingAverageExample.kt rename to examples/src/main/java/org/firstinspires/ftc/teamcode/utils/MovingAverageExample.kt diff --git a/examples/TeamCode/src/main/res/raw/readme.md b/examples/src/main/res/raw/readme.md similarity index 100% rename from examples/TeamCode/src/main/res/raw/readme.md rename to examples/src/main/res/raw/readme.md diff --git a/examples/TeamCode/src/main/res/values/strings.xml b/examples/src/main/res/values/strings.xml similarity index 100% rename from examples/TeamCode/src/main/res/values/strings.xml rename to examples/src/main/res/values/strings.xml diff --git a/examples/TeamCode/src/main/res/xml/teamwebcamcalibrations.xml b/examples/src/main/res/xml/teamwebcamcalibrations.xml similarity index 100% rename from examples/TeamCode/src/main/res/xml/teamwebcamcalibrations.xml rename to examples/src/main/res/xml/teamwebcamcalibrations.xml diff --git a/library/Battery/build.gradle.kts b/library/Battery/build.gradle.kts index 73fe956..5f7fbbf 100644 --- a/library/Battery/build.gradle.kts +++ b/library/Battery/build.gradle.kts @@ -2,14 +2,15 @@ val pluginNamespace = "com.bylazar.battery" val pluginVersion = "1.0.3" plugins { - id("com.android.library") - id("org.jetbrains.kotlin.android") + id("dev.frozenmilk.android-library") version "11.1.0-1.1.1" id("com.bylazar.svelte-assets") id("dev.frozenmilk.publish") version "0.0.5" id("dev.frozenmilk.doc") version "0.0.5" id("dev.frozenmilk.build-meta-data") version "0.0.2" } +android.namespace = pluginNamespace + svelteAssets { assetsPath = assetPathForPlugin(pluginNamespace) } @@ -30,44 +31,17 @@ meta { registerField("version", "String") { "\"$version\"" } } -android { - namespace = pluginNamespace - - defaultConfig { - compileSdk = 34 - minSdk = 24 - } - - buildTypes { - getByName("release") { - isMinifyEnabled = false - } - } - - compileOptions { - sourceCompatibility = JavaVersion.VERSION_11 - targetCompatibility = JavaVersion.VERSION_11 - } - - kotlinOptions { - jvmTarget = "11" - } - - publishing { - singleVariant("release") {} +ftc { + kotlin() + sdk { + compileOnly(RobotCore) + compileOnly(FtcCommon) + compileOnly(RobotServer) + compileOnly(Hardware) } } dependencies { - compileOnly("org.firstinspires.ftc:Inspection:11.0.0") - compileOnly("org.firstinspires.ftc:Blocks:11.0.0") - compileOnly("org.firstinspires.ftc:RobotCore:11.0.0") - compileOnly("org.firstinspires.ftc:RobotServer:11.0.0") - compileOnly("org.firstinspires.ftc:OnBotJava:11.0.0") - compileOnly("org.firstinspires.ftc:Hardware:11.0.0") - compileOnly("org.firstinspires.ftc:FtcCommon:11.0.0") - compileOnly("org.firstinspires.ftc:Vision:11.0.0") - compileOnly(project(":Panels")) } diff --git a/library/Battery/web/bun.lock b/library/Battery/web/bun.lock index b8db93a..0d865e4 100644 --- a/library/Battery/web/bun.lock +++ b/library/Battery/web/bun.lock @@ -1,5 +1,6 @@ { "lockfileVersion": 1, + "configVersion": 0, "workspaces": { "": { "name": "panels-plugin", diff --git a/library/CameraStream/build.gradle.kts b/library/CameraStream/build.gradle.kts index bfe297d..6769911 100644 --- a/library/CameraStream/build.gradle.kts +++ b/library/CameraStream/build.gradle.kts @@ -2,14 +2,15 @@ val pluginNamespace = "com.bylazar.camerastream" val pluginVersion = "1.0.0" plugins { - id("com.android.library") - id("org.jetbrains.kotlin.android") + id("dev.frozenmilk.android-library") version "11.1.0-1.1.1" id("com.bylazar.svelte-assets") id("dev.frozenmilk.publish") version "0.0.5" id("dev.frozenmilk.doc") version "0.0.5" id("dev.frozenmilk.build-meta-data") version "0.0.2" } +android.namespace = pluginNamespace + svelteAssets { assetsPath = assetPathForPlugin(pluginNamespace) } @@ -30,45 +31,17 @@ meta { registerField("version", "String") { "\"${dairyPublishing.version}\"" } } -android { - namespace = pluginNamespace - - defaultConfig { - compileSdk = 34 - minSdk = 24 - } - - buildTypes { - getByName("release") { - isMinifyEnabled = false - } - } - - compileOptions { - sourceCompatibility = JavaVersion.VERSION_11 - targetCompatibility = JavaVersion.VERSION_11 - } - - kotlinOptions { - jvmTarget = "11" - } - - publishing { - singleVariant("release") {} +ftc { + kotlin() + sdk { + compileOnly(RobotCore) + compileOnly(FtcCommon) } } dependencies { - compileOnly("org.firstinspires.ftc:Inspection:11.0.0") - compileOnly("org.firstinspires.ftc:Blocks:11.0.0") - compileOnly("org.firstinspires.ftc:RobotCore:11.0.0") - compileOnly("org.firstinspires.ftc:RobotServer:11.0.0") - compileOnly("org.firstinspires.ftc:OnBotJava:11.0.0") - compileOnly("org.firstinspires.ftc:Hardware:11.0.0") - compileOnly("org.firstinspires.ftc:FtcCommon:11.0.0") - compileOnly("org.firstinspires.ftc:Vision:11.0.0") - compileOnly(project(":Panels")) + // TODO: remove? implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.1") } diff --git a/library/CameraStream/web/bun.lock b/library/CameraStream/web/bun.lock index b8db93a..0d865e4 100644 --- a/library/CameraStream/web/bun.lock +++ b/library/CameraStream/web/bun.lock @@ -1,5 +1,6 @@ { "lockfileVersion": 1, + "configVersion": 0, "workspaces": { "": { "name": "panels-plugin", diff --git a/library/Capture/build.gradle.kts b/library/Capture/build.gradle.kts index f9b9759..b7f9885 100644 --- a/library/Capture/build.gradle.kts +++ b/library/Capture/build.gradle.kts @@ -2,14 +2,15 @@ val pluginNamespace = "com.bylazar.capture" val pluginVersion = "1.0.3" plugins { - id("com.android.library") - id("org.jetbrains.kotlin.android") + id("dev.frozenmilk.android-library") version "11.1.0-1.1.1" id("com.bylazar.svelte-assets") id("dev.frozenmilk.publish") version "0.0.5" id("dev.frozenmilk.doc") version "0.0.5" id("dev.frozenmilk.build-meta-data") version "0.0.2" } +android.namespace = pluginNamespace + svelteAssets { assetsPath = assetPathForPlugin(pluginNamespace) } @@ -30,44 +31,15 @@ meta { registerField("version", "String") { "\"$version\"" } } -android { - namespace = pluginNamespace - - defaultConfig { - compileSdk = 34 - minSdk = 24 - } - - buildTypes { - getByName("release") { - isMinifyEnabled = false - } - } - - compileOptions { - sourceCompatibility = JavaVersion.VERSION_11 - targetCompatibility = JavaVersion.VERSION_11 - } - - kotlinOptions { - jvmTarget = "11" - } - - publishing { - singleVariant("release") {} +ftc { + kotlin() + sdk { + compileOnly(RobotCore) + compileOnly(FtcCommon) } } dependencies { - compileOnly("org.firstinspires.ftc:Inspection:11.0.0") - compileOnly("org.firstinspires.ftc:Blocks:11.0.0") - compileOnly("org.firstinspires.ftc:RobotCore:11.0.0") - compileOnly("org.firstinspires.ftc:RobotServer:11.0.0") - compileOnly("org.firstinspires.ftc:OnBotJava:11.0.0") - compileOnly("org.firstinspires.ftc:Hardware:11.0.0") - compileOnly("org.firstinspires.ftc:FtcCommon:11.0.0") - compileOnly("org.firstinspires.ftc:Vision:11.0.0") - compileOnly(project(":Panels")) } diff --git a/library/Capture/web/bun.lock b/library/Capture/web/bun.lock index d9b44e6..56b2352 100644 --- a/library/Capture/web/bun.lock +++ b/library/Capture/web/bun.lock @@ -1,5 +1,6 @@ { "lockfileVersion": 1, + "configVersion": 0, "workspaces": { "": { "name": "panels-plugin", diff --git a/library/Configurables/build.gradle.kts b/library/Configurables/build.gradle.kts index c9a57c5..18f6aee 100644 --- a/library/Configurables/build.gradle.kts +++ b/library/Configurables/build.gradle.kts @@ -2,14 +2,15 @@ val pluginNamespace = "com.bylazar.configurables" val pluginVersion = "1.0.5" plugins { - id("com.android.library") - id("org.jetbrains.kotlin.android") + id("dev.frozenmilk.android-library") version "11.1.0-1.1.1" id("com.bylazar.svelte-assets") id("dev.frozenmilk.publish") version "0.0.5" id("dev.frozenmilk.doc") version "0.0.5" id("dev.frozenmilk.build-meta-data") version "0.0.2" } +android.namespace = pluginNamespace + svelteAssets { assetsPath = assetPathForPlugin(pluginNamespace) } @@ -30,50 +31,19 @@ meta { registerField("version", "String") { "\"${dairyPublishing.version}\"" } } -android { - namespace = pluginNamespace - - defaultConfig { - compileSdk = 34 - minSdk = 24 - } - - buildTypes { - getByName("release") { - isMinifyEnabled = false - } - } - - compileOptions { - sourceCompatibility = JavaVersion.VERSION_11 - targetCompatibility = JavaVersion.VERSION_11 +ftc { + kotlin() + sdk { + compileOnly(RobotCore) + compileOnly(FtcCommon) + compileOnly(RobotServer) } - - kotlinOptions { - jvmTarget = "11" - } - - publishing { - singleVariant("release") {} + dairy { + implementation(Sloth) } } -repositories { - maven("https://repo.dairy.foundation/releases") -} - dependencies { - compileOnly("org.firstinspires.ftc:Inspection:11.0.0") - compileOnly("org.firstinspires.ftc:Blocks:11.0.0") - compileOnly("org.firstinspires.ftc:RobotCore:11.0.0") - compileOnly("org.firstinspires.ftc:RobotServer:11.0.0") - compileOnly("org.firstinspires.ftc:OnBotJava:11.0.0") - compileOnly("org.firstinspires.ftc:Hardware:11.0.0") - compileOnly("org.firstinspires.ftc:FtcCommon:11.0.0") - compileOnly("org.firstinspires.ftc:Vision:11.0.0") - - implementation("dev.frozenmilk.sinister:Sloth:0.2.4") - compileOnly(project(":Panels")) } diff --git a/library/Configurables/web/bun.lock b/library/Configurables/web/bun.lock index b8db93a..0d865e4 100644 --- a/library/Configurables/web/bun.lock +++ b/library/Configurables/web/bun.lock @@ -1,5 +1,6 @@ { "lockfileVersion": 1, + "configVersion": 0, "workspaces": { "": { "name": "panels-plugin", diff --git a/library/Docs/build.gradle.kts b/library/Docs/build.gradle.kts index 3611171..89c69dc 100644 --- a/library/Docs/build.gradle.kts +++ b/library/Docs/build.gradle.kts @@ -2,14 +2,15 @@ val pluginNamespace = "com.bylazar.docs" val pluginVersion = "1.0.5" plugins { - id("com.android.library") - id("org.jetbrains.kotlin.android") + id("dev.frozenmilk.android-library") version "11.1.0-1.1.1" id("com.bylazar.svelte-assets") id("dev.frozenmilk.publish") version "0.0.5" id("dev.frozenmilk.doc") version "0.0.5" id("dev.frozenmilk.build-meta-data") version "0.0.2" } +android.namespace = pluginNamespace + svelteAssets { assetsPath = assetPathForPlugin(pluginNamespace) } @@ -30,44 +31,16 @@ meta { registerField("version", "String") { "\"$version\"" } } -android { - namespace = pluginNamespace - - defaultConfig { - compileSdk = 34 - minSdk = 24 - } - - buildTypes { - getByName("release") { - isMinifyEnabled = false - } - } - - compileOptions { - sourceCompatibility = JavaVersion.VERSION_11 - targetCompatibility = JavaVersion.VERSION_11 - } - - kotlinOptions { - jvmTarget = "11" - } - - publishing { - singleVariant("release") {} +ftc { + kotlin() + sdk { + compileOnly(RobotCore) + compileOnly(FtcCommon) + compileOnly(RobotServer) } } dependencies { - compileOnly("org.firstinspires.ftc:Inspection:11.0.0") - compileOnly("org.firstinspires.ftc:Blocks:11.0.0") - compileOnly("org.firstinspires.ftc:RobotCore:11.0.0") - compileOnly("org.firstinspires.ftc:RobotServer:11.0.0") - compileOnly("org.firstinspires.ftc:OnBotJava:11.0.0") - compileOnly("org.firstinspires.ftc:Hardware:11.0.0") - compileOnly("org.firstinspires.ftc:FtcCommon:11.0.0") - compileOnly("org.firstinspires.ftc:Vision:11.0.0") - compileOnly(project(":Panels")) } diff --git a/library/Docs/web/bun.lock b/library/Docs/web/bun.lock index b8db93a..0d865e4 100644 --- a/library/Docs/web/bun.lock +++ b/library/Docs/web/bun.lock @@ -1,5 +1,6 @@ { "lockfileVersion": 1, + "configVersion": 0, "workspaces": { "": { "name": "panels-plugin", diff --git a/library/ExamplePlugin/build.gradle.kts b/library/ExamplePlugin/build.gradle.kts index b2fa8eb..a920a95 100644 --- a/library/ExamplePlugin/build.gradle.kts +++ b/library/ExamplePlugin/build.gradle.kts @@ -2,14 +2,15 @@ val pluginNamespace = "com.bylazar.exampleplugin" val pluginVersion = "0.0.14" plugins { - id("com.android.library") - id("org.jetbrains.kotlin.android") + id("dev.frozenmilk.android-library") version "11.1.0-1.1.1" id("com.bylazar.svelte-assets") id("dev.frozenmilk.publish") version "0.0.5" id("dev.frozenmilk.doc") version "0.0.5" id("dev.frozenmilk.build-meta-data") version "0.0.2" } +android.namespace = pluginNamespace + svelteAssets { assetsPath = assetPathForPlugin(pluginNamespace) } @@ -30,44 +31,16 @@ meta { registerField("version", "String") { "\"$version\"" } } -android { - namespace = pluginNamespace - - defaultConfig { - compileSdk = 34 - minSdk = 24 - } - - buildTypes { - getByName("release") { - isMinifyEnabled = false - } - } - - compileOptions { - sourceCompatibility = JavaVersion.VERSION_11 - targetCompatibility = JavaVersion.VERSION_11 - } - - kotlinOptions { - jvmTarget = "11" - } - - publishing { - singleVariant("release") {} +ftc { + kotlin() + sdk { + compileOnly(RobotCore) + compileOnly(FtcCommon) + compileOnly(RobotServer) } } dependencies { - compileOnly("org.firstinspires.ftc:Inspection:11.0.0") - compileOnly("org.firstinspires.ftc:Blocks:11.0.0") - compileOnly("org.firstinspires.ftc:RobotCore:11.0.0") - compileOnly("org.firstinspires.ftc:RobotServer:11.0.0") - compileOnly("org.firstinspires.ftc:OnBotJava:11.0.0") - compileOnly("org.firstinspires.ftc:Hardware:11.0.0") - compileOnly("org.firstinspires.ftc:FtcCommon:11.0.0") - compileOnly("org.firstinspires.ftc:Vision:11.0.0") - compileOnly(project(":Panels")) } diff --git a/library/ExamplePlugin/web/bun.lock b/library/ExamplePlugin/web/bun.lock index d9b44e6..56b2352 100644 --- a/library/ExamplePlugin/web/bun.lock +++ b/library/ExamplePlugin/web/bun.lock @@ -1,5 +1,6 @@ { "lockfileVersion": 1, + "configVersion": 0, "workspaces": { "": { "name": "panels-plugin", diff --git a/library/Field/build.gradle.kts b/library/Field/build.gradle.kts index f031cab..225e8fe 100644 --- a/library/Field/build.gradle.kts +++ b/library/Field/build.gradle.kts @@ -2,14 +2,15 @@ val pluginNamespace = "com.bylazar.field" val pluginVersion = "1.0.6" plugins { - id("com.android.library") - id("org.jetbrains.kotlin.android") + id("dev.frozenmilk.android-library") version "11.1.0-1.1.1" id("com.bylazar.svelte-assets") id("dev.frozenmilk.publish") version "0.0.5" id("dev.frozenmilk.doc") version "0.0.5" id("dev.frozenmilk.build-meta-data") version "0.0.2" } +android.namespace = pluginNamespace + svelteAssets { assetsPath = assetPathForPlugin(pluginNamespace) } @@ -30,44 +31,16 @@ meta { registerField("version", "String") { "\"$version\"" } } -android { - namespace = pluginNamespace - - defaultConfig { - compileSdk = 34 - minSdk = 24 - } - - buildTypes { - getByName("release") { - isMinifyEnabled = false - } - } - - compileOptions { - sourceCompatibility = JavaVersion.VERSION_11 - targetCompatibility = JavaVersion.VERSION_11 - } - - kotlinOptions { - jvmTarget = "11" - } - - publishing { - singleVariant("release") {} +ftc { + kotlin() + sdk { + compileOnly(RobotCore) + compileOnly(FtcCommon) + compileOnly(RobotServer) } } dependencies { - compileOnly("org.firstinspires.ftc:Inspection:11.0.0") - compileOnly("org.firstinspires.ftc:Blocks:11.0.0") - compileOnly("org.firstinspires.ftc:RobotCore:11.0.0") - compileOnly("org.firstinspires.ftc:RobotServer:11.0.0") - compileOnly("org.firstinspires.ftc:OnBotJava:11.0.0") - compileOnly("org.firstinspires.ftc:Hardware:11.0.0") - compileOnly("org.firstinspires.ftc:FtcCommon:11.0.0") - compileOnly("org.firstinspires.ftc:Vision:11.0.0") - compileOnly(project(":Panels")) } diff --git a/library/Field/web/bun.lock b/library/Field/web/bun.lock index b8db93a..0d865e4 100644 --- a/library/Field/web/bun.lock +++ b/library/Field/web/bun.lock @@ -1,5 +1,6 @@ { "lockfileVersion": 1, + "configVersion": 0, "workspaces": { "": { "name": "panels-plugin", diff --git a/library/FtcRobotController/build.gradle b/library/FtcRobotController/build.gradle deleted file mode 100644 index 86fdd50..0000000 --- a/library/FtcRobotController/build.gradle +++ /dev/null @@ -1,25 +0,0 @@ -import java.text.SimpleDateFormat - -apply plugin: 'com.android.library' - -apply from: '../build.dependencies.gradle' -android { - - defaultConfig { - minSdkVersion 24 - //noinspection ExpiredTargetSdkVersion - targetSdkVersion 28 - compileSdk = 34 - buildConfigField "String", "APP_BUILD_TIME", '"' + (new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ", Locale.ROOT).format(new Date())) + '"' - } - - buildFeatures { - buildConfig = true - } - - compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 - } - namespace = 'com.qualcomm.ftcrobotcontroller' -} diff --git a/library/FtcRobotController/src/main/AndroidManifest.xml b/library/FtcRobotController/src/main/AndroidManifest.xml deleted file mode 100644 index 24038bf..0000000 --- a/library/FtcRobotController/src/main/AndroidManifest.xml +++ /dev/null @@ -1,79 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/BasicOmniOpMode_Linear.java b/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/BasicOmniOpMode_Linear.java deleted file mode 100644 index 1d14dfb..0000000 --- a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/BasicOmniOpMode_Linear.java +++ /dev/null @@ -1,167 +0,0 @@ -/* Copyright (c) 2021 FIRST. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted (subject to the limitations in the disclaimer below) provided that - * the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list - * of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this - * list of conditions and the following disclaimer in the documentation and/or - * other materials provided with the distribution. - * - * Neither the name of FIRST nor the names of its contributors may be used to endorse or - * promote products derived from this software without specific prior written permission. - * - * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS - * LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.firstinspires.ftc.robotcontroller.external.samples; - -import com.qualcomm.robotcore.eventloop.opmode.Disabled; -import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode; -import com.qualcomm.robotcore.eventloop.opmode.TeleOp; -import com.qualcomm.robotcore.hardware.DcMotor; -import com.qualcomm.robotcore.util.ElapsedTime; - -/* - * This file contains an example of a Linear "OpMode". - * An OpMode is a 'program' that runs in either the autonomous or the teleop period of an FTC match. - * The names of OpModes appear on the menu of the FTC Driver Station. - * When a selection is made from the menu, the corresponding OpMode is executed. - * - * This particular OpMode illustrates driving a 4-motor Omni-Directional (or Holonomic) robot. - * This code will work with either a Mecanum-Drive or an X-Drive train. - * Both of these drives are illustrated at https://gm0.org/en/latest/docs/robot-design/drivetrains/holonomic.html - * Note that a Mecanum drive must display an X roller-pattern when viewed from above. - * - * Also note that it is critical to set the correct rotation direction for each motor. See details below. - * - * Holonomic drives provide the ability for the robot to move in three axes (directions) simultaneously. - * Each motion axis is controlled by one Joystick axis. - * - * 1) Axial: Driving forward and backward Left-joystick Forward/Backward - * 2) Lateral: Strafing right and left Left-joystick Right and Left - * 3) Yaw: Rotating Clockwise and counter clockwise Right-joystick Right and Left - * - * This code is written assuming that the right-side motors need to be reversed for the robot to drive forward. - * When you first test your robot, if it moves backward when you push the left stick forward, then you must flip - * the direction of all 4 motors (see code below). - * - * Use Android Studio to Copy this Class, and Paste it into your team's code folder with a new name. - * Remove or comment out the @Disabled line to add this OpMode to the Driver Station OpMode list - */ - -@TeleOp(name="Basic: Omni Linear OpMode", group="Linear OpMode") -@Disabled -public class BasicOmniOpMode_Linear extends LinearOpMode { - - // Declare OpMode members for each of the 4 motors. - private ElapsedTime runtime = new ElapsedTime(); - private DcMotor leftFrontDrive = null; - private DcMotor leftBackDrive = null; - private DcMotor rightFrontDrive = null; - private DcMotor rightBackDrive = null; - - @Override - public void runOpMode() { - - // Initialize the hardware variables. Note that the strings used here must correspond - // to the names assigned during the robot configuration step on the DS or RC devices. - leftFrontDrive = hardwareMap.get(DcMotor.class, "left_front_drive"); - leftBackDrive = hardwareMap.get(DcMotor.class, "left_back_drive"); - rightFrontDrive = hardwareMap.get(DcMotor.class, "right_front_drive"); - rightBackDrive = hardwareMap.get(DcMotor.class, "right_back_drive"); - - // ######################################################################################## - // !!! IMPORTANT Drive Information. Test your motor directions. !!!!! - // ######################################################################################## - // Most robots need the motors on one side to be reversed to drive forward. - // The motor reversals shown here are for a "direct drive" robot (the wheels turn the same direction as the motor shaft) - // If your robot has additional gear reductions or uses a right-angled drive, it's important to ensure - // that your motors are turning in the correct direction. So, start out with the reversals here, BUT - // when you first test your robot, push the left joystick forward and observe the direction the wheels turn. - // Reverse the direction (flip FORWARD <-> REVERSE ) of any wheel that runs backward - // Keep testing until ALL the wheels move the robot forward when you push the left joystick forward. - leftFrontDrive.setDirection(DcMotor.Direction.REVERSE); - leftBackDrive.setDirection(DcMotor.Direction.REVERSE); - rightFrontDrive.setDirection(DcMotor.Direction.FORWARD); - rightBackDrive.setDirection(DcMotor.Direction.FORWARD); - - // Wait for the game to start (driver presses START) - telemetry.addData("Status", "Initialized"); - telemetry.update(); - - waitForStart(); - runtime.reset(); - - // run until the end of the match (driver presses STOP) - while (opModeIsActive()) { - double max; - - // POV Mode uses left joystick to go forward & strafe, and right joystick to rotate. - double axial = -gamepad1.left_stick_y; // Note: pushing stick forward gives negative value - double lateral = gamepad1.left_stick_x; - double yaw = gamepad1.right_stick_x; - - // Combine the joystick requests for each axis-motion to determine each wheel's power. - // Set up a variable for each drive wheel to save the power level for telemetry. - double leftFrontPower = axial + lateral + yaw; - double rightFrontPower = axial - lateral - yaw; - double leftBackPower = axial - lateral + yaw; - double rightBackPower = axial + lateral - yaw; - - // Normalize the values so no wheel power exceeds 100% - // This ensures that the robot maintains the desired motion. - max = Math.max(Math.abs(leftFrontPower), Math.abs(rightFrontPower)); - max = Math.max(max, Math.abs(leftBackPower)); - max = Math.max(max, Math.abs(rightBackPower)); - - if (max > 1.0) { - leftFrontPower /= max; - rightFrontPower /= max; - leftBackPower /= max; - rightBackPower /= max; - } - - // This is test code: - // - // Uncomment the following code to test your motor directions. - // Each button should make the corresponding motor run FORWARD. - // 1) First get all the motors to take to correct positions on the robot - // by adjusting your Robot Configuration if necessary. - // 2) Then make sure they run in the correct direction by modifying the - // the setDirection() calls above. - // Once the correct motors move in the correct direction re-comment this code. - - /* - leftFrontPower = gamepad1.x ? 1.0 : 0.0; // X gamepad - leftBackPower = gamepad1.a ? 1.0 : 0.0; // A gamepad - rightFrontPower = gamepad1.y ? 1.0 : 0.0; // Y gamepad - rightBackPower = gamepad1.b ? 1.0 : 0.0; // B gamepad - */ - - // Send calculated power to wheels - leftFrontDrive.setPower(leftFrontPower); - rightFrontDrive.setPower(rightFrontPower); - leftBackDrive.setPower(leftBackPower); - rightBackDrive.setPower(rightBackPower); - - // Show the elapsed game time and wheel power. - telemetry.addData("Status", "Run Time: " + runtime.toString()); - telemetry.addData("Front left/Right", "%4.2f, %4.2f", leftFrontPower, rightFrontPower); - telemetry.addData("Back left/Right", "%4.2f, %4.2f", leftBackPower, rightBackPower); - telemetry.update(); - } - }} diff --git a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/BasicOpMode_Iterative.java b/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/BasicOpMode_Iterative.java deleted file mode 100644 index 6504e58..0000000 --- a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/BasicOpMode_Iterative.java +++ /dev/null @@ -1,140 +0,0 @@ -/* Copyright (c) 2017 FIRST. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted (subject to the limitations in the disclaimer below) provided that - * the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list - * of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this - * list of conditions and the following disclaimer in the documentation and/or - * other materials provided with the distribution. - * - * Neither the name of FIRST nor the names of its contributors may be used to endorse or - * promote products derived from this software without specific prior written permission. - * - * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS - * LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.firstinspires.ftc.robotcontroller.external.samples; - -import com.qualcomm.robotcore.eventloop.opmode.Disabled; -import com.qualcomm.robotcore.eventloop.opmode.OpMode; -import com.qualcomm.robotcore.eventloop.opmode.TeleOp; -import com.qualcomm.robotcore.hardware.DcMotor; -import com.qualcomm.robotcore.util.ElapsedTime; -import com.qualcomm.robotcore.util.Range; - -/* - * This file contains an example of an iterative (Non-Linear) "OpMode". - * An OpMode is a 'program' that runs in either the autonomous or the teleop period of an FTC match. - * The names of OpModes appear on the menu of the FTC Driver Station. - * When a selection is made from the menu, the corresponding OpMode - * class is instantiated on the Robot Controller and executed. - * - * This particular OpMode just executes a basic Tank Drive Teleop for a two wheeled robot - * It includes all the skeletal structure that all iterative OpModes contain. - * - * Use Android Studio to Copy this Class, and Paste it into your team's code folder with a new name. - * Remove or comment out the @Disabled line to add this OpMode to the Driver Station OpMode list - */ - -@TeleOp(name="Basic: Iterative OpMode", group="Iterative OpMode") -@Disabled -public class BasicOpMode_Iterative extends OpMode -{ - // Declare OpMode members. - private ElapsedTime runtime = new ElapsedTime(); - private DcMotor leftDrive = null; - private DcMotor rightDrive = null; - - /* - * Code to run ONCE when the driver hits INIT - */ - @Override - public void init() { - telemetry.addData("Status", "Initialized"); - - // Initialize the hardware variables. Note that the strings used here as parameters - // to 'get' must correspond to the names assigned during the robot configuration - // step (using the FTC Robot Controller app on the phone). - leftDrive = hardwareMap.get(DcMotor.class, "left_drive"); - rightDrive = hardwareMap.get(DcMotor.class, "right_drive"); - - // To drive forward, most robots need the motor on one side to be reversed, because the axles point in opposite directions. - // Pushing the left stick forward MUST make robot go forward. So adjust these two lines based on your first test drive. - // Note: The settings here assume direct drive on left and right wheels. Gear Reduction or 90 Deg drives may require direction flips - leftDrive.setDirection(DcMotor.Direction.REVERSE); - rightDrive.setDirection(DcMotor.Direction.FORWARD); - - // Tell the driver that initialization is complete. - telemetry.addData("Status", "Initialized"); - } - - /* - * Code to run REPEATEDLY after the driver hits INIT, but before they hit START - */ - @Override - public void init_loop() { - } - - /* - * Code to run ONCE when the driver hits START - */ - @Override - public void start() { - runtime.reset(); - } - - /* - * Code to run REPEATEDLY after the driver hits START but before they hit STOP - */ - @Override - public void loop() { - // Setup a variable for each drive wheel to save power level for telemetry - double leftPower; - double rightPower; - - // Choose to drive using either Tank Mode, or POV Mode - // Comment out the method that's not used. The default below is POV. - - // POV Mode uses left stick to go forward, and right stick to turn. - // - This uses basic math to combine motions and is easier to drive straight. - double drive = -gamepad1.left_stick_y; - double turn = gamepad1.right_stick_x; - leftPower = Range.clip(drive + turn, -1.0, 1.0) ; - rightPower = Range.clip(drive - turn, -1.0, 1.0) ; - - // Tank Mode uses one stick to control each wheel. - // - This requires no math, but it is hard to drive forward slowly and keep straight. - // leftPower = -gamepad1.left_stick_y ; - // rightPower = -gamepad1.right_stick_y ; - - // Send calculated power to wheels - leftDrive.setPower(leftPower); - rightDrive.setPower(rightPower); - - // Show the elapsed game time and wheel power. - telemetry.addData("Status", "Run Time: " + runtime.toString()); - telemetry.addData("Motors", "left (%.2f), right (%.2f)", leftPower, rightPower); - } - - /* - * Code to run ONCE after the driver hits STOP - */ - @Override - public void stop() { - } - -} diff --git a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/BasicOpMode_Linear.java b/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/BasicOpMode_Linear.java deleted file mode 100644 index ab0bb25..0000000 --- a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/BasicOpMode_Linear.java +++ /dev/null @@ -1,115 +0,0 @@ -/* Copyright (c) 2017 FIRST. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted (subject to the limitations in the disclaimer below) provided that - * the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list - * of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this - * list of conditions and the following disclaimer in the documentation and/or - * other materials provided with the distribution. - * - * Neither the name of FIRST nor the names of its contributors may be used to endorse or - * promote products derived from this software without specific prior written permission. - * - * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS - * LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.firstinspires.ftc.robotcontroller.external.samples; - -import com.qualcomm.robotcore.eventloop.opmode.Disabled; -import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode; -import com.qualcomm.robotcore.eventloop.opmode.TeleOp; -import com.qualcomm.robotcore.hardware.DcMotor; -import com.qualcomm.robotcore.util.ElapsedTime; -import com.qualcomm.robotcore.util.Range; - - -/* - * This file contains an minimal example of a Linear "OpMode". An OpMode is a 'program' that runs in either - * the autonomous or the teleop period of an FTC match. The names of OpModes appear on the menu - * of the FTC Driver Station. When a selection is made from the menu, the corresponding OpMode - * class is instantiated on the Robot Controller and executed. - * - * This particular OpMode just executes a basic Tank Drive Teleop for a two wheeled robot - * It includes all the skeletal structure that all linear OpModes contain. - * - * Use Android Studio to Copy this Class, and Paste it into your team's code folder with a new name. - * Remove or comment out the @Disabled line to add this OpMode to the Driver Station OpMode list - */ - -@TeleOp(name="Basic: Linear OpMode", group="Linear OpMode") -@Disabled -public class BasicOpMode_Linear extends LinearOpMode { - - // Declare OpMode members. - private ElapsedTime runtime = new ElapsedTime(); - private DcMotor leftDrive = null; - private DcMotor rightDrive = null; - - @Override - public void runOpMode() { - telemetry.addData("Status", "Initialized"); - telemetry.update(); - - // Initialize the hardware variables. Note that the strings used here as parameters - // to 'get' must correspond to the names assigned during the robot configuration - // step (using the FTC Robot Controller app on the phone). - leftDrive = hardwareMap.get(DcMotor.class, "left_drive"); - rightDrive = hardwareMap.get(DcMotor.class, "right_drive"); - - // To drive forward, most robots need the motor on one side to be reversed, because the axles point in opposite directions. - // Pushing the left stick forward MUST make robot go forward. So adjust these two lines based on your first test drive. - // Note: The settings here assume direct drive on left and right wheels. Gear Reduction or 90 Deg drives may require direction flips - leftDrive.setDirection(DcMotor.Direction.REVERSE); - rightDrive.setDirection(DcMotor.Direction.FORWARD); - - // Wait for the game to start (driver presses START) - waitForStart(); - runtime.reset(); - - // run until the end of the match (driver presses STOP) - while (opModeIsActive()) { - - // Setup a variable for each drive wheel to save power level for telemetry - double leftPower; - double rightPower; - - // Choose to drive using either Tank Mode, or POV Mode - // Comment out the method that's not used. The default below is POV. - - // POV Mode uses left stick to go forward, and right stick to turn. - // - This uses basic math to combine motions and is easier to drive straight. - double drive = -gamepad1.left_stick_y; - double turn = gamepad1.right_stick_x; - leftPower = Range.clip(drive + turn, -1.0, 1.0) ; - rightPower = Range.clip(drive - turn, -1.0, 1.0) ; - - // Tank Mode uses one stick to control each wheel. - // - This requires no math, but it is hard to drive forward slowly and keep straight. - // leftPower = -gamepad1.left_stick_y ; - // rightPower = -gamepad1.right_stick_y ; - - // Send calculated power to wheels - leftDrive.setPower(leftPower); - rightDrive.setPower(rightPower); - - // Show the elapsed game time and wheel power. - telemetry.addData("Status", "Run Time: " + runtime.toString()); - telemetry.addData("Motors", "left (%.2f), right (%.2f)", leftPower, rightPower); - telemetry.update(); - } - } -} diff --git a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptAprilTag.java b/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptAprilTag.java deleted file mode 100644 index 8ec77dd..0000000 --- a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptAprilTag.java +++ /dev/null @@ -1,217 +0,0 @@ -/* Copyright (c) 2023 FIRST. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted (subject to the limitations in the disclaimer below) provided that - * the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list - * of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this - * list of conditions and the following disclaimer in the documentation and/or - * other materials provided with the distribution. - * - * Neither the name of FIRST nor the names of its contributors may be used to endorse or - * promote products derived from this software without specific prior written permission. - * - * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS - * LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.firstinspires.ftc.robotcontroller.external.samples; - -import android.util.Size; -import com.qualcomm.robotcore.eventloop.opmode.Disabled; -import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode; -import com.qualcomm.robotcore.eventloop.opmode.TeleOp; -import org.firstinspires.ftc.robotcore.external.hardware.camera.BuiltinCameraDirection; -import org.firstinspires.ftc.robotcore.external.hardware.camera.WebcamName; -import org.firstinspires.ftc.vision.VisionPortal; -import org.firstinspires.ftc.vision.apriltag.AprilTagDetection; -import org.firstinspires.ftc.vision.apriltag.AprilTagProcessor; - -import java.util.List; - -/* - * This OpMode illustrates the basics of AprilTag recognition and pose estimation, - * including Java Builder structures for specifying Vision parameters. - * - * For an introduction to AprilTags, see the FTC-DOCS link below: - * https://ftc-docs.firstinspires.org/en/latest/apriltag/vision_portal/apriltag_intro/apriltag-intro.html - * - * In this sample, any visible tag ID will be detected and displayed, but only tags that are included in the default - * "TagLibrary" will have their position and orientation information displayed. This default TagLibrary contains - * the current Season's AprilTags and a small set of "test Tags" in the high number range. - * - * When an AprilTag in the TagLibrary is detected, the SDK provides location and orientation of the tag, relative to the camera. - * This information is provided in the "ftcPose" member of the returned "detection", and is explained in the ftc-docs page linked below. - * https://ftc-docs.firstinspires.org/apriltag-detection-values - * - * To experiment with using AprilTags to navigate, try out these two driving samples: - * RobotAutoDriveToAprilTagOmni and RobotAutoDriveToAprilTagTank - * - * There are many "default" VisionPortal and AprilTag configuration parameters that may be overridden if desired. - * These default parameters are shown as comments in the code below. - * - * Use Android Studio to Copy this Class, and Paste it into your team's code folder with a new name. - * Remove or comment out the @Disabled line to add this OpMode to the Driver Station OpMode list. - */ -@TeleOp(name = "Concept: AprilTag", group = "Concept") -@Disabled -public class ConceptAprilTag extends LinearOpMode { - - private static final boolean USE_WEBCAM = true; // true for webcam, false for phone camera - - /** - * The variable to store our instance of the AprilTag processor. - */ - private AprilTagProcessor aprilTag; - - /** - * The variable to store our instance of the vision portal. - */ - private VisionPortal visionPortal; - - @Override - public void runOpMode() { - - initAprilTag(); - - // Wait for the DS start button to be touched. - telemetry.addData("DS preview on/off", "3 dots, Camera Stream"); - telemetry.addData(">", "Touch START to start OpMode"); - telemetry.update(); - waitForStart(); - - if (opModeIsActive()) { - while (opModeIsActive()) { - - telemetryAprilTag(); - - // Push telemetry to the Driver Station. - telemetry.update(); - - // Save CPU resources; can resume streaming when needed. - if (gamepad1.dpad_down) { - visionPortal.stopStreaming(); - } else if (gamepad1.dpad_up) { - visionPortal.resumeStreaming(); - } - - // Share the CPU. - sleep(20); - } - } - - // Save more CPU resources when camera is no longer needed. - visionPortal.close(); - - } // end method runOpMode() - - /** - * Initialize the AprilTag processor. - */ - private void initAprilTag() { - - // Create the AprilTag processor. - aprilTag = new AprilTagProcessor.Builder() - - // The following default settings are available to un-comment and edit as needed. - //.setDrawAxes(false) - //.setDrawCubeProjection(false) - //.setDrawTagOutline(true) - //.setTagFamily(AprilTagProcessor.TagFamily.TAG_36h11) - //.setTagLibrary(AprilTagGameDatabase.getCenterStageTagLibrary()) - //.setOutputUnits(DistanceUnit.INCH, AngleUnit.DEGREES) - - // == CAMERA CALIBRATION == - // If you do not manually specify calibration parameters, the SDK will attempt - // to load a predefined calibration for your camera. - //.setLensIntrinsics(578.272, 578.272, 402.145, 221.506) - // ... these parameters are fx, fy, cx, cy. - - .build(); - - // Adjust Image Decimation to trade-off detection-range for detection-rate. - // eg: Some typical detection data using a Logitech C920 WebCam - // Decimation = 1 .. Detect 2" Tag from 10 feet away at 10 Frames per second - // Decimation = 2 .. Detect 2" Tag from 6 feet away at 22 Frames per second - // Decimation = 3 .. Detect 2" Tag from 4 feet away at 30 Frames Per Second (default) - // Decimation = 3 .. Detect 5" Tag from 10 feet away at 30 Frames Per Second (default) - // Note: Decimation can be changed on-the-fly to adapt during a match. - //aprilTag.setDecimation(3); - - // Create the vision portal by using a builder. - VisionPortal.Builder builder = new VisionPortal.Builder(); - - // Set the camera (webcam vs. built-in RC phone camera). - if (USE_WEBCAM) { - builder.setCamera(hardwareMap.get(WebcamName.class, "Webcam 1")); - } else { - builder.setCamera(BuiltinCameraDirection.BACK); - } - - // Choose a camera resolution. Not all cameras support all resolutions. - //builder.setCameraResolution(new Size(640, 480)); - - // Enable the RC preview (LiveView). Set "false" to omit camera monitoring. - //builder.enableLiveView(true); - - // Set the stream format; MJPEG uses less bandwidth than default YUY2. - //builder.setStreamFormat(VisionPortal.StreamFormat.YUY2); - - // Choose whether or not LiveView stops if no processors are enabled. - // If set "true", monitor shows solid orange screen if no processors enabled. - // If set "false", monitor shows camera view without annotations. - //builder.setAutoStopLiveView(false); - - // Set and enable the processor. - builder.addProcessor(aprilTag); - - // Build the Vision Portal, using the above settings. - visionPortal = builder.build(); - - // Disable or re-enable the aprilTag processor at any time. - //visionPortal.setProcessorEnabled(aprilTag, true); - - } // end method initAprilTag() - - - /** - * Add telemetry about AprilTag detections. - */ - private void telemetryAprilTag() { - - List currentDetections = aprilTag.getDetections(); - telemetry.addData("# AprilTags Detected", currentDetections.size()); - - // Step through the list of detections and display info for each one. - for (AprilTagDetection detection : currentDetections) { - if (detection.metadata != null) { - telemetry.addLine(String.format("\n==== (ID %d) %s", detection.id, detection.metadata.name)); - telemetry.addLine(String.format("XYZ %6.1f %6.1f %6.1f (inch)", detection.ftcPose.x, detection.ftcPose.y, detection.ftcPose.z)); - telemetry.addLine(String.format("PRY %6.1f %6.1f %6.1f (deg)", detection.ftcPose.pitch, detection.ftcPose.roll, detection.ftcPose.yaw)); - telemetry.addLine(String.format("RBE %6.1f %6.1f %6.1f (inch, deg, deg)", detection.ftcPose.range, detection.ftcPose.bearing, detection.ftcPose.elevation)); - } else { - telemetry.addLine(String.format("\n==== (ID %d) Unknown", detection.id)); - telemetry.addLine(String.format("Center %6.0f %6.0f (pixels)", detection.center.x, detection.center.y)); - } - } // end for() loop - - // Add "key" information to telemetry - telemetry.addLine("\nkey:\nXYZ = X (Right), Y (Forward), Z (Up) dist."); - telemetry.addLine("PRY = Pitch, Roll & Yaw (XYZ Rotation)"); - telemetry.addLine("RBE = Range, Bearing & Elevation"); - - } // end method telemetryAprilTag() - -} // end class diff --git a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptAprilTagEasy.java b/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptAprilTagEasy.java deleted file mode 100644 index 12dcf6e..0000000 --- a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptAprilTagEasy.java +++ /dev/null @@ -1,163 +0,0 @@ -/* Copyright (c) 2023 FIRST. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted (subject to the limitations in the disclaimer below) provided that - * the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list - * of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this - * list of conditions and the following disclaimer in the documentation and/or - * other materials provided with the distribution. - * - * Neither the name of FIRST nor the names of its contributors may be used to endorse or - * promote products derived from this software without specific prior written permission. - * - * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS - * LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.firstinspires.ftc.robotcontroller.external.samples; - -import com.qualcomm.robotcore.eventloop.opmode.Disabled; -import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode; -import com.qualcomm.robotcore.eventloop.opmode.TeleOp; -import org.firstinspires.ftc.robotcore.external.hardware.camera.BuiltinCameraDirection; -import org.firstinspires.ftc.robotcore.external.hardware.camera.WebcamName; -import org.firstinspires.ftc.vision.VisionPortal; -import org.firstinspires.ftc.vision.apriltag.AprilTagDetection; -import org.firstinspires.ftc.vision.apriltag.AprilTagProcessor; - -import java.util.List; - -/* - * This OpMode illustrates the basics of AprilTag recognition and pose estimation, using - * the easy way. - * - * For an introduction to AprilTags, see the FTC-DOCS link below: - * https://ftc-docs.firstinspires.org/en/latest/apriltag/vision_portal/apriltag_intro/apriltag-intro.html - * - * In this sample, any visible tag ID will be detected and displayed, but only tags that are included in the default - * "TagLibrary" will have their position and orientation information displayed. This default TagLibrary contains - * the current Season's AprilTags and a small set of "test Tags" in the high number range. - * - * When an AprilTag in the TagLibrary is detected, the SDK provides location and orientation of the tag, relative to the camera. - * This information is provided in the "ftcPose" member of the returned "detection", and is explained in the ftc-docs page linked below. - * https://ftc-docs.firstinspires.org/apriltag-detection-values - * - * To experiment with using AprilTags to navigate, try out these two driving samples: - * RobotAutoDriveToAprilTagOmni and RobotAutoDriveToAprilTagTank - * - * Use Android Studio to Copy this Class, and Paste it into your team's code folder with a new name. - * Remove or comment out the @Disabled line to add this OpMode to the Driver Station OpMode list. - */ -@TeleOp(name = "Concept: AprilTag Easy", group = "Concept") -@Disabled -public class ConceptAprilTagEasy extends LinearOpMode { - - private static final boolean USE_WEBCAM = true; // true for webcam, false for phone camera - - /** - * The variable to store our instance of the AprilTag processor. - */ - private AprilTagProcessor aprilTag; - - /** - * The variable to store our instance of the vision portal. - */ - private VisionPortal visionPortal; - - @Override - public void runOpMode() { - - initAprilTag(); - - // Wait for the DS start button to be touched. - telemetry.addData("DS preview on/off", "3 dots, Camera Stream"); - telemetry.addData(">", "Touch START to start OpMode"); - telemetry.update(); - waitForStart(); - - if (opModeIsActive()) { - while (opModeIsActive()) { - - telemetryAprilTag(); - - // Push telemetry to the Driver Station. - telemetry.update(); - - // Save CPU resources; can resume streaming when needed. - if (gamepad1.dpad_down) { - visionPortal.stopStreaming(); - } else if (gamepad1.dpad_up) { - visionPortal.resumeStreaming(); - } - - // Share the CPU. - sleep(20); - } - } - - // Save more CPU resources when camera is no longer needed. - visionPortal.close(); - - } // end method runOpMode() - - /** - * Initialize the AprilTag processor. - */ - private void initAprilTag() { - - // Create the AprilTag processor the easy way. - aprilTag = AprilTagProcessor.easyCreateWithDefaults(); - - // Create the vision portal the easy way. - if (USE_WEBCAM) { - visionPortal = VisionPortal.easyCreateWithDefaults( - hardwareMap.get(WebcamName.class, "Webcam 1"), aprilTag); - } else { - visionPortal = VisionPortal.easyCreateWithDefaults( - BuiltinCameraDirection.BACK, aprilTag); - } - - } // end method initAprilTag() - - /** - * Add telemetry about AprilTag detections. - */ - private void telemetryAprilTag() { - - List currentDetections = aprilTag.getDetections(); - telemetry.addData("# AprilTags Detected", currentDetections.size()); - - // Step through the list of detections and display info for each one. - for (AprilTagDetection detection : currentDetections) { - if (detection.metadata != null) { - telemetry.addLine(String.format("\n==== (ID %d) %s", detection.id, detection.metadata.name)); - telemetry.addLine(String.format("XYZ %6.1f %6.1f %6.1f (inch)", detection.ftcPose.x, detection.ftcPose.y, detection.ftcPose.z)); - telemetry.addLine(String.format("PRY %6.1f %6.1f %6.1f (deg)", detection.ftcPose.pitch, detection.ftcPose.roll, detection.ftcPose.yaw)); - telemetry.addLine(String.format("RBE %6.1f %6.1f %6.1f (inch, deg, deg)", detection.ftcPose.range, detection.ftcPose.bearing, detection.ftcPose.elevation)); - } else { - telemetry.addLine(String.format("\n==== (ID %d) Unknown", detection.id)); - telemetry.addLine(String.format("Center %6.0f %6.0f (pixels)", detection.center.x, detection.center.y)); - } - } // end for() loop - - // Add "key" information to telemetry - telemetry.addLine("\nkey:\nXYZ = X (Right), Y (Forward), Z (Up) dist."); - telemetry.addLine("PRY = Pitch, Roll & Yaw (XYZ Rotation)"); - telemetry.addLine("RBE = Range, Bearing & Elevation"); - - } // end method telemetryAprilTag() - -} // end class diff --git a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptAprilTagLocalization.java b/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptAprilTagLocalization.java deleted file mode 100644 index d90261e..0000000 --- a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptAprilTagLocalization.java +++ /dev/null @@ -1,247 +0,0 @@ -/* Copyright (c) 2024 Dryw Wade. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted (subject to the limitations in the disclaimer below) provided that - * the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list - * of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this - * list of conditions and the following disclaimer in the documentation and/or - * other materials provided with the distribution. - * - * Neither the name of FIRST nor the names of its contributors may be used to endorse or - * promote products derived from this software without specific prior written permission. - * - * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS - * LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.firstinspires.ftc.robotcontroller.external.samples; - -import com.qualcomm.robotcore.eventloop.opmode.Disabled; -import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode; -import com.qualcomm.robotcore.eventloop.opmode.TeleOp; - -import org.firstinspires.ftc.robotcore.external.hardware.camera.BuiltinCameraDirection; -import org.firstinspires.ftc.robotcore.external.hardware.camera.WebcamName; -import org.firstinspires.ftc.robotcore.external.navigation.AngleUnit; -import org.firstinspires.ftc.robotcore.external.navigation.DistanceUnit; -import org.firstinspires.ftc.robotcore.external.navigation.Position; -import org.firstinspires.ftc.robotcore.external.navigation.YawPitchRollAngles; -import org.firstinspires.ftc.vision.VisionPortal; -import org.firstinspires.ftc.vision.apriltag.AprilTagDetection; -import org.firstinspires.ftc.vision.apriltag.AprilTagProcessor; - -import java.util.List; - -/* - * This OpMode illustrates the basics of AprilTag based localization. - * - * For an introduction to AprilTags, see the FTC-DOCS link below: - * https://ftc-docs.firstinspires.org/en/latest/apriltag/vision_portal/apriltag_intro/apriltag-intro.html - * - * In this sample, any visible tag ID will be detected and displayed, but only tags that are included in the default - * "TagLibrary" will be used to compute the robot's location and orientation. This default TagLibrary contains - * the current Season's AprilTags and a small set of "test Tags" in the high number range. - * - * When an AprilTag in the TagLibrary is detected, the SDK provides location and orientation of the robot, relative to the field origin. - * This information is provided in the "robotPose" member of the returned "detection". - * - * To learn about the Field Coordinate System that is defined for FTC (and used by this OpMode), see the FTC-DOCS link below: - * https://ftc-docs.firstinspires.org/en/latest/game_specific_resources/field_coordinate_system/field-coordinate-system.html - * - * Use Android Studio to Copy this Class, and Paste it into your team's code folder with a new name. - * Remove or comment out the @Disabled line to add this OpMode to the Driver Station OpMode list. - */ -@TeleOp(name = "Concept: AprilTag Localization", group = "Concept") -@Disabled -public class ConceptAprilTagLocalization extends LinearOpMode { - - private static final boolean USE_WEBCAM = true; // true for webcam, false for phone camera - - /** - * Variables to store the position and orientation of the camera on the robot. Setting these - * values requires a definition of the axes of the camera and robot: - * - * Camera axes: - * Origin location: Center of the lens - * Axes orientation: +x right, +y down, +z forward (from camera's perspective) - * - * Robot axes (this is typical, but you can define this however you want): - * Origin location: Center of the robot at field height - * Axes orientation: +x right, +y forward, +z upward - * - * Position: - * If all values are zero (no translation), that implies the camera is at the center of the - * robot. Suppose your camera is positioned 5 inches to the left, 7 inches forward, and 12 - * inches above the ground - you would need to set the position to (-5, 7, 12). - * - * Orientation: - * If all values are zero (no rotation), that implies the camera is pointing straight up. In - * most cases, you'll need to set the pitch to -90 degrees (rotation about the x-axis), meaning - * the camera is horizontal. Use a yaw of 0 if the camera is pointing forwards, +90 degrees if - * it's pointing straight left, -90 degrees for straight right, etc. You can also set the roll - * to +/-90 degrees if it's vertical, or 180 degrees if it's upside-down. - */ - private Position cameraPosition = new Position(DistanceUnit.INCH, - 0, 0, 0, 0); - private YawPitchRollAngles cameraOrientation = new YawPitchRollAngles(AngleUnit.DEGREES, - 0, -90, 0, 0); - - /** - * The variable to store our instance of the AprilTag processor. - */ - private AprilTagProcessor aprilTag; - - /** - * The variable to store our instance of the vision portal. - */ - private VisionPortal visionPortal; - - @Override - public void runOpMode() { - - initAprilTag(); - - // Wait for the DS start button to be touched. - telemetry.addData("DS preview on/off", "3 dots, Camera Stream"); - telemetry.addData(">", "Touch START to start OpMode"); - telemetry.update(); - waitForStart(); - - while (opModeIsActive()) { - - telemetryAprilTag(); - - // Push telemetry to the Driver Station. - telemetry.update(); - - // Save CPU resources; can resume streaming when needed. - if (gamepad1.dpad_down) { - visionPortal.stopStreaming(); - } else if (gamepad1.dpad_up) { - visionPortal.resumeStreaming(); - } - - // Share the CPU. - sleep(20); - } - - // Save more CPU resources when camera is no longer needed. - visionPortal.close(); - - } // end method runOpMode() - - /** - * Initialize the AprilTag processor. - */ - private void initAprilTag() { - - // Create the AprilTag processor. - aprilTag = new AprilTagProcessor.Builder() - - // The following default settings are available to un-comment and edit as needed. - //.setDrawAxes(false) - //.setDrawCubeProjection(false) - //.setDrawTagOutline(true) - //.setTagFamily(AprilTagProcessor.TagFamily.TAG_36h11) - //.setTagLibrary(AprilTagGameDatabase.getCenterStageTagLibrary()) - //.setOutputUnits(DistanceUnit.INCH, AngleUnit.DEGREES) - .setCameraPose(cameraPosition, cameraOrientation) - - // == CAMERA CALIBRATION == - // If you do not manually specify calibration parameters, the SDK will attempt - // to load a predefined calibration for your camera. - //.setLensIntrinsics(578.272, 578.272, 402.145, 221.506) - // ... these parameters are fx, fy, cx, cy. - - .build(); - - // Adjust Image Decimation to trade-off detection-range for detection-rate. - // eg: Some typical detection data using a Logitech C920 WebCam - // Decimation = 1 .. Detect 2" Tag from 10 feet away at 10 Frames per second - // Decimation = 2 .. Detect 2" Tag from 6 feet away at 22 Frames per second - // Decimation = 3 .. Detect 2" Tag from 4 feet away at 30 Frames Per Second (default) - // Decimation = 3 .. Detect 5" Tag from 10 feet away at 30 Frames Per Second (default) - // Note: Decimation can be changed on-the-fly to adapt during a match. - //aprilTag.setDecimation(3); - - // Create the vision portal by using a builder. - VisionPortal.Builder builder = new VisionPortal.Builder(); - - // Set the camera (webcam vs. built-in RC phone camera). - if (USE_WEBCAM) { - builder.setCamera(hardwareMap.get(WebcamName.class, "Webcam 1")); - } else { - builder.setCamera(BuiltinCameraDirection.BACK); - } - - // Choose a camera resolution. Not all cameras support all resolutions. - //builder.setCameraResolution(new Size(640, 480)); - - // Enable the RC preview (LiveView). Set "false" to omit camera monitoring. - //builder.enableLiveView(true); - - // Set the stream format; MJPEG uses less bandwidth than default YUY2. - //builder.setStreamFormat(VisionPortal.StreamFormat.YUY2); - - // Choose whether or not LiveView stops if no processors are enabled. - // If set "true", monitor shows solid orange screen if no processors enabled. - // If set "false", monitor shows camera view without annotations. - //builder.setAutoStopLiveView(false); - - // Set and enable the processor. - builder.addProcessor(aprilTag); - - // Build the Vision Portal, using the above settings. - visionPortal = builder.build(); - - // Disable or re-enable the aprilTag processor at any time. - //visionPortal.setProcessorEnabled(aprilTag, true); - - } // end method initAprilTag() - - /** - * Add telemetry about AprilTag detections. - */ - private void telemetryAprilTag() { - - List currentDetections = aprilTag.getDetections(); - telemetry.addData("# AprilTags Detected", currentDetections.size()); - - // Step through the list of detections and display info for each one. - for (AprilTagDetection detection : currentDetections) { - if (detection.metadata != null) { - telemetry.addLine(String.format("\n==== (ID %d) %s", detection.id, detection.metadata.name)); - telemetry.addLine(String.format("XYZ %6.1f %6.1f %6.1f (inch)", - detection.robotPose.getPosition().x, - detection.robotPose.getPosition().y, - detection.robotPose.getPosition().z)); - telemetry.addLine(String.format("PRY %6.1f %6.1f %6.1f (deg)", - detection.robotPose.getOrientation().getPitch(AngleUnit.DEGREES), - detection.robotPose.getOrientation().getRoll(AngleUnit.DEGREES), - detection.robotPose.getOrientation().getYaw(AngleUnit.DEGREES))); - } else { - telemetry.addLine(String.format("\n==== (ID %d) Unknown", detection.id)); - telemetry.addLine(String.format("Center %6.0f %6.0f (pixels)", detection.center.x, detection.center.y)); - } - } // end for() loop - - // Add "key" information to telemetry - telemetry.addLine("\nkey:\nXYZ = X (Right), Y (Forward), Z (Up) dist."); - telemetry.addLine("PRY = Pitch, Roll & Yaw (XYZ Rotation)"); - - } // end method telemetryAprilTag() - -} // end class diff --git a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptAprilTagMultiPortal.java b/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptAprilTagMultiPortal.java deleted file mode 100644 index da5cc3e..0000000 --- a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptAprilTagMultiPortal.java +++ /dev/null @@ -1,104 +0,0 @@ -/* Copyright (c) 2024 FIRST. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted (subject to the limitations in the disclaimer below) provided that - * the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list - * of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this - * list of conditions and the following disclaimer in the documentation and/or - * other materials provided with the distribution. - * - * Neither the name of FIRST nor the names of its contributors may be used to endorse or - * promote products derived from this software without specific prior written permission. - * - * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS - * LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.firstinspires.ftc.robotcontroller.external.samples; - -import com.qualcomm.robotcore.eventloop.opmode.Disabled; -import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode; -import com.qualcomm.robotcore.eventloop.opmode.TeleOp; - -import org.firstinspires.ftc.robotcore.external.hardware.camera.BuiltinCameraDirection; -import org.firstinspires.ftc.robotcore.external.hardware.camera.WebcamName; -import org.firstinspires.ftc.vision.VisionPortal; -import org.firstinspires.ftc.vision.apriltag.AprilTagProcessor; - -/** - * This OpMode demonstrates the basics of using multiple vision portals simultaneously - */ -@TeleOp(name = "Concept: AprilTagMultiPortal", group = "Concept") -@Disabled -public class ConceptAprilTagMultiPortal extends LinearOpMode -{ - VisionPortal portal1; - VisionPortal portal2; - - AprilTagProcessor aprilTagProcessor1; - AprilTagProcessor aprilTagProcessor2; - - @Override - public void runOpMode() throws InterruptedException - { - // Because we want to show two camera feeds simultaneously, we need to inform - // the SDK that we want it to split the camera monitor area into two smaller - // areas for us. It will then give us View IDs which we can pass to the individual - // vision portals to allow them to properly hook into the UI in tandem. - int[] viewIds = VisionPortal.makeMultiPortalView(2, VisionPortal.MultiPortalLayout.VERTICAL); - - // We extract the two view IDs from the array to make our lives a little easier later. - // NB: the array is 2 long because we asked for 2 portals up above. - int portal1ViewId = viewIds[0]; - int portal2ViewId = viewIds[1]; - - // If we want to run AprilTag detection on two portals simultaneously, - // we need to create two distinct instances of the AprilTag processor, - // one for each portal. If you want to see more detail about different - // options that you have when creating these processors, go check out - // the ConceptAprilTag OpMode. - aprilTagProcessor1 = AprilTagProcessor.easyCreateWithDefaults(); - aprilTagProcessor2 = AprilTagProcessor.easyCreateWithDefaults(); - - // Now we build both portals. The CRITICAL thing to notice here is the call to - // setLiveViewContainerId(), where we pass in the IDs we received earlier from - // makeMultiPortalView(). - portal1 = new VisionPortal.Builder() - .setCamera(hardwareMap.get(WebcamName.class, "Webcam 1")) - .setLiveViewContainerId(portal1ViewId) - .addProcessor(aprilTagProcessor1) - .build(); - portal2 = new VisionPortal.Builder() - .setCamera(hardwareMap.get(WebcamName.class, "Webcam 2")) - .setLiveViewContainerId(portal2ViewId) - .addProcessor(aprilTagProcessor2) - .build(); - - waitForStart(); - - // Main Loop - while (opModeIsActive()) - { - // Just show some basic telemetry to demonstrate both processors are working in parallel - // on their respective cameras. If you want to see more detail about the information you - // can get back from the processor, you should look at ConceptAprilTag. - telemetry.addData("Number of tags in Camera 1", aprilTagProcessor1.getDetections().size()); - telemetry.addData("Number of tags in Camera 2", aprilTagProcessor2.getDetections().size()); - telemetry.update(); - sleep(20); - } - } -} diff --git a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptAprilTagOptimizeExposure.java b/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptAprilTagOptimizeExposure.java deleted file mode 100644 index adee952..0000000 --- a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptAprilTagOptimizeExposure.java +++ /dev/null @@ -1,246 +0,0 @@ -/* Copyright (c) 2023 FIRST. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted (subject to the limitations in the disclaimer below) provided that - * the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list - * of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this - * list of conditions and the following disclaimer in the documentation and/or - * other materials provided with the distribution. - * - * Neither the name of FIRST nor the names of its contributors may be used to endorse or - * promote products derived from this software without specific prior written permission. - * - * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS - * LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.firstinspires.ftc.robotcontroller.external.samples; - -import com.qualcomm.robotcore.eventloop.opmode.Disabled; -import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode; -import com.qualcomm.robotcore.eventloop.opmode.TeleOp; -import com.qualcomm.robotcore.util.Range; -import org.firstinspires.ftc.robotcore.external.hardware.camera.WebcamName; -import org.firstinspires.ftc.robotcore.external.hardware.camera.controls.ExposureControl; -import org.firstinspires.ftc.robotcore.external.hardware.camera.controls.GainControl; -import org.firstinspires.ftc.vision.VisionPortal; -import org.firstinspires.ftc.vision.apriltag.AprilTagDetection; -import org.firstinspires.ftc.vision.apriltag.AprilTagProcessor; - -import java.util.List; -import java.util.concurrent.TimeUnit; - -/* - * This OpMode determines the best Exposure for minimizing image motion-blur on a Webcam - * Note that it is not possible to control the exposure for a Phone Camera, so if you are using a Phone for the Robot Controller - * this OpMode/Feature only applies to an externally connected Webcam - * - * The goal is to determine the smallest (shortest) Exposure value that still provides reliable Tag Detection. - * Starting with the minimum Exposure and maximum Gain, the exposure is slowly increased until the Tag is - * detected reliably from the likely operational distance. - * - * - * The best way to run this optimization is to view the camera preview screen while changing the exposure and gains. - * - * To do this, you need to view the RobotController screen directly (not from Driver Station) - * This can be done directly from a RC phone screen (if you are using an external Webcam), but for a Control Hub you must either plug an - * HDMI monitor into the Control Hub HDMI port, or use an external viewer program like ScrCpy (https://scrcpy.org/) - * - * Use Android Studio to Copy this Class, and Paste it into the TeamCode/src/main/java/org/firstinspires/ftc/teamcode folder. - * Remove or comment out the @Disabled line to add this OpMode to the Driver Station OpMode list. - */ - -@TeleOp(name="Optimize AprilTag Exposure", group = "Concept") -@Disabled -public class ConceptAprilTagOptimizeExposure extends LinearOpMode -{ - private VisionPortal visionPortal = null; // Used to manage the video source. - private AprilTagProcessor aprilTag; // Used for managing the AprilTag detection process. - private int myExposure ; - private int minExposure ; - private int maxExposure ; - private int myGain ; - private int minGain ; - private int maxGain ; - - boolean thisExpUp = false; - boolean thisExpDn = false; - boolean thisGainUp = false; - boolean thisGainDn = false; - - boolean lastExpUp = false; - boolean lastExpDn = false; - boolean lastGainUp = false; - boolean lastGainDn = false; - @Override public void runOpMode() - { - // Initialize the Apriltag Detection process - initAprilTag(); - - // Establish Min and Max Gains and Exposure. Then set a low exposure with high gain - getCameraSetting(); - myExposure = Math.min(5, minExposure); - myGain = maxGain; - setManualExposure(myExposure, myGain); - - // Wait for the match to begin. - telemetry.addData("Camera preview on/off", "3 dots, Camera Stream"); - telemetry.addData(">", "Touch START to start OpMode"); - telemetry.update(); - waitForStart(); - - while (opModeIsActive()) - { - telemetry.addLine("Find lowest Exposure that gives reliable detection."); - telemetry.addLine("Use Left bump/trig to adjust Exposure."); - telemetry.addLine("Use Right bump/trig to adjust Gain.\n"); - - // Display how many Tags Detected - List currentDetections = aprilTag.getDetections(); - int numTags = currentDetections.size(); - if (numTags > 0 ) - telemetry.addData("Tag", "####### %d Detected ######", currentDetections.size()); - else - telemetry.addData("Tag", "----------- none - ----------"); - - telemetry.addData("Exposure","%d (%d - %d)", myExposure, minExposure, maxExposure); - telemetry.addData("Gain","%d (%d - %d)", myGain, minGain, maxGain); - telemetry.update(); - - // check to see if we need to change exposure or gain. - thisExpUp = gamepad1.left_bumper; - thisExpDn = gamepad1.left_trigger > 0.25; - thisGainUp = gamepad1.right_bumper; - thisGainDn = gamepad1.right_trigger > 0.25; - - // look for clicks to change exposure - if (thisExpUp && !lastExpUp) { - myExposure = Range.clip(myExposure + 1, minExposure, maxExposure); - setManualExposure(myExposure, myGain); - } else if (thisExpDn && !lastExpDn) { - myExposure = Range.clip(myExposure - 1, minExposure, maxExposure); - setManualExposure(myExposure, myGain); - } - - // look for clicks to change the gain - if (thisGainUp && !lastGainUp) { - myGain = Range.clip(myGain + 1, minGain, maxGain ); - setManualExposure(myExposure, myGain); - } else if (thisGainDn && !lastGainDn) { - myGain = Range.clip(myGain - 1, minGain, maxGain ); - setManualExposure(myExposure, myGain); - } - - lastExpUp = thisExpUp; - lastExpDn = thisExpDn; - lastGainUp = thisGainUp; - lastGainDn = thisGainDn; - - sleep(20); - } - } - - /** - * Initialize the AprilTag processor. - */ - private void initAprilTag() { - // Create the AprilTag processor by using a builder. - aprilTag = new AprilTagProcessor.Builder().build(); - - // Create the WEBCAM vision portal by using a builder. - visionPortal = new VisionPortal.Builder() - .setCamera(hardwareMap.get(WebcamName.class, "Webcam 1")) - .addProcessor(aprilTag) - .build(); - } - - /* - Manually set the camera gain and exposure. - Can only be called AFTER calling initAprilTag(); - Returns true if controls are set. - */ - private boolean setManualExposure(int exposureMS, int gain) { - // Ensure Vision Portal has been setup. - if (visionPortal == null) { - return false; - } - - // Wait for the camera to be open - if (visionPortal.getCameraState() != VisionPortal.CameraState.STREAMING) { - telemetry.addData("Camera", "Waiting"); - telemetry.update(); - while (!isStopRequested() && (visionPortal.getCameraState() != VisionPortal.CameraState.STREAMING)) { - sleep(20); - } - telemetry.addData("Camera", "Ready"); - telemetry.update(); - } - - // Set camera controls unless we are stopping. - if (!isStopRequested()) - { - // Set exposure. Make sure we are in Manual Mode for these values to take effect. - ExposureControl exposureControl = visionPortal.getCameraControl(ExposureControl.class); - if (exposureControl.getMode() != ExposureControl.Mode.Manual) { - exposureControl.setMode(ExposureControl.Mode.Manual); - sleep(50); - } - exposureControl.setExposure((long)exposureMS, TimeUnit.MILLISECONDS); - sleep(20); - - // Set Gain. - GainControl gainControl = visionPortal.getCameraControl(GainControl.class); - gainControl.setGain(gain); - sleep(20); - return (true); - } else { - return (false); - } - } - - /* - Read this camera's minimum and maximum Exposure and Gain settings. - Can only be called AFTER calling initAprilTag(); - */ - private void getCameraSetting() { - // Ensure Vision Portal has been setup. - if (visionPortal == null) { - return; - } - - // Wait for the camera to be open - if (visionPortal.getCameraState() != VisionPortal.CameraState.STREAMING) { - telemetry.addData("Camera", "Waiting"); - telemetry.update(); - while (!isStopRequested() && (visionPortal.getCameraState() != VisionPortal.CameraState.STREAMING)) { - sleep(20); - } - telemetry.addData("Camera", "Ready"); - telemetry.update(); - } - - // Get camera control values unless we are stopping. - if (!isStopRequested()) { - ExposureControl exposureControl = visionPortal.getCameraControl(ExposureControl.class); - minExposure = (int)exposureControl.getMinExposure(TimeUnit.MILLISECONDS) + 1; - maxExposure = (int)exposureControl.getMaxExposure(TimeUnit.MILLISECONDS); - - GainControl gainControl = visionPortal.getCameraControl(GainControl.class); - minGain = gainControl.getMinGain(); - maxGain = gainControl.getMaxGain(); - } - } -} diff --git a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptAprilTagSwitchableCameras.java b/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptAprilTagSwitchableCameras.java deleted file mode 100644 index 7ee1064..0000000 --- a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptAprilTagSwitchableCameras.java +++ /dev/null @@ -1,196 +0,0 @@ -/* Copyright (c) 2023 FIRST. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted (subject to the limitations in the disclaimer below) provided that - * the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list - * of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this - * list of conditions and the following disclaimer in the documentation and/or - * other materials provided with the distribution. - * - * Neither the name of FIRST nor the names of its contributors may be used to endorse or - * promote products derived from this software without specific prior written permission. - * - * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS - * LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.firstinspires.ftc.robotcontroller.external.samples; - -import com.qualcomm.robotcore.eventloop.opmode.Disabled; -import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode; -import com.qualcomm.robotcore.eventloop.opmode.TeleOp; -import org.firstinspires.ftc.robotcore.external.ClassFactory; -import org.firstinspires.ftc.robotcore.external.hardware.camera.CameraName; -import org.firstinspires.ftc.robotcore.external.hardware.camera.WebcamName; -import org.firstinspires.ftc.vision.VisionPortal; -import org.firstinspires.ftc.vision.VisionPortal.CameraState; -import org.firstinspires.ftc.vision.apriltag.AprilTagDetection; -import org.firstinspires.ftc.vision.apriltag.AprilTagProcessor; - -import java.util.List; - -/* - * This OpMode illustrates the basics of AprilTag recognition and pose estimation, using - * two webcams. - * - * Use Android Studio to Copy this Class, and Paste it into your team's code folder with a new name. - * Remove or comment out the @Disabled line to add this OpMode to the Driver Station OpMode list. - */ -@TeleOp(name = "Concept: AprilTag Switchable Cameras", group = "Concept") -@Disabled -public class ConceptAprilTagSwitchableCameras extends LinearOpMode { - - /* - * Variables used for switching cameras. - */ - private WebcamName webcam1, webcam2; - private boolean oldLeftBumper; - private boolean oldRightBumper; - - /** - * The variable to store our instance of the AprilTag processor. - */ - private AprilTagProcessor aprilTag; - - /** - * The variable to store our instance of the vision portal. - */ - private VisionPortal visionPortal; - - @Override - public void runOpMode() { - - initAprilTag(); - - // Wait for the DS start button to be touched. - telemetry.addData("DS preview on/off", "3 dots, Camera Stream"); - telemetry.addData(">", "Touch START to start OpMode"); - telemetry.update(); - waitForStart(); - - if (opModeIsActive()) { - while (opModeIsActive()) { - - telemetryCameraSwitching(); - telemetryAprilTag(); - - // Push telemetry to the Driver Station. - telemetry.update(); - - // Save CPU resources; can resume streaming when needed. - if (gamepad1.dpad_down) { - visionPortal.stopStreaming(); - } else if (gamepad1.dpad_up) { - visionPortal.resumeStreaming(); - } - - doCameraSwitching(); - - // Share the CPU. - sleep(20); - } - } - - // Save more CPU resources when camera is no longer needed. - visionPortal.close(); - - } // end runOpMode() - - /** - * Initialize the AprilTag processor. - */ - private void initAprilTag() { - - // Create the AprilTag processor by using a builder. - aprilTag = new AprilTagProcessor.Builder().build(); - - webcam1 = hardwareMap.get(WebcamName.class, "Webcam 1"); - webcam2 = hardwareMap.get(WebcamName.class, "Webcam 2"); - CameraName switchableCamera = ClassFactory.getInstance() - .getCameraManager().nameForSwitchableCamera(webcam1, webcam2); - - // Create the vision portal by using a builder. - visionPortal = new VisionPortal.Builder() - .setCamera(switchableCamera) - .addProcessor(aprilTag) - .build(); - - } // end method initAprilTag() - - /** - * Add telemetry about camera switching. - */ - private void telemetryCameraSwitching() { - - if (visionPortal.getActiveCamera().equals(webcam1)) { - telemetry.addData("activeCamera", "Webcam 1"); - telemetry.addData("Press RightBumper", "to switch to Webcam 2"); - } else { - telemetry.addData("activeCamera", "Webcam 2"); - telemetry.addData("Press LeftBumper", "to switch to Webcam 1"); - } - - } // end method telemetryCameraSwitching() - - /** - * Add telemetry about AprilTag detections. - */ - private void telemetryAprilTag() { - - List currentDetections = aprilTag.getDetections(); - telemetry.addData("# AprilTags Detected", currentDetections.size()); - - // Step through the list of detections and display info for each one. - for (AprilTagDetection detection : currentDetections) { - if (detection.metadata != null) { - telemetry.addLine(String.format("\n==== (ID %d) %s", detection.id, detection.metadata.name)); - telemetry.addLine(String.format("XYZ %6.1f %6.1f %6.1f (inch)", detection.ftcPose.x, detection.ftcPose.y, detection.ftcPose.z)); - telemetry.addLine(String.format("PRY %6.1f %6.1f %6.1f (deg)", detection.ftcPose.pitch, detection.ftcPose.roll, detection.ftcPose.yaw)); - telemetry.addLine(String.format("RBE %6.1f %6.1f %6.1f (inch, deg, deg)", detection.ftcPose.range, detection.ftcPose.bearing, detection.ftcPose.elevation)); - } else { - telemetry.addLine(String.format("\n==== (ID %d) Unknown", detection.id)); - telemetry.addLine(String.format("Center %6.0f %6.0f (pixels)", detection.center.x, detection.center.y)); - } - } // end for() loop - - // Add "key" information to telemetry - telemetry.addLine("\nkey:\nXYZ = X (Right), Y (Forward), Z (Up) dist."); - telemetry.addLine("PRY = Pitch, Roll & Yaw (XYZ Rotation)"); - telemetry.addLine("RBE = Range, Bearing & Elevation"); - - } // end method telemetryAprilTag() - - /** - * Set the active camera according to input from the gamepad. - */ - private void doCameraSwitching() { - if (visionPortal.getCameraState() == CameraState.STREAMING) { - // If the left bumper is pressed, use Webcam 1. - // If the right bumper is pressed, use Webcam 2. - boolean newLeftBumper = gamepad1.left_bumper; - boolean newRightBumper = gamepad1.right_bumper; - if (newLeftBumper && !oldLeftBumper) { - visionPortal.setActiveCamera(webcam1); - } else if (newRightBumper && !oldRightBumper) { - visionPortal.setActiveCamera(webcam2); - } - oldLeftBumper = newLeftBumper; - oldRightBumper = newRightBumper; - } - - } // end method doCameraSwitching() - -} // end class diff --git a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptExploringIMUOrientation.java b/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptExploringIMUOrientation.java deleted file mode 100644 index 751d54f..0000000 --- a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptExploringIMUOrientation.java +++ /dev/null @@ -1,184 +0,0 @@ -/* -Copyright (c) 2022 REV Robotics, FIRST - -All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted (subject to the limitations in the disclaimer below) provided that -the following conditions are met: - -Redistributions of source code must retain the above copyright notice, this list -of conditions and the following disclaimer. - -Redistributions in binary form must reproduce the above copyright notice, this -list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. - -Neither the name of REV Robotics nor the names of its contributors may be used to -endorse or promote products derived from this software without specific prior -written permission. - -NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS -LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR -TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF -THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ -package org.firstinspires.ftc.robotcontroller.external.samples; - -import com.qualcomm.hardware.rev.RevHubOrientationOnRobot; -import com.qualcomm.robotcore.eventloop.opmode.Disabled; -import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode; -import com.qualcomm.robotcore.eventloop.opmode.TeleOp; -import com.qualcomm.robotcore.hardware.IMU; -import org.firstinspires.ftc.robotcore.external.navigation.AngleUnit; -import org.firstinspires.ftc.robotcore.external.navigation.AngularVelocity; -import org.firstinspires.ftc.robotcore.external.navigation.YawPitchRollAngles; - -/* - * This OpMode demonstrates the impact of setting the IMU orientation correctly or incorrectly. This - * code assumes there is an IMU configured with the name "imu". - * - * Note: This OpMode is more of a tool than a code sample. The User Interface portion of this code - * goes beyond simply showing how to interface to the IMU.
- * For a minimal example of interfacing to an IMU, please see the SensorIMUOrthogonal or SensorIMUNonOrthogonal sample OpModes. - * - * This OpMode enables you to re-specify the Hub Mounting orientation dynamically by using gamepad controls. - * While doing so, the sample will display how Pitch, Roll and Yaw angles change as the hub is moved. - * - * The gamepad controls let you change the two parameters that specify how the Control/Expansion Hub is mounted.
- * The first parameter specifies which direction the printed logo on the Hub is pointing.
- * The second parameter specifies which direction the USB connector on the Hub is pointing.
- * All directions are relative to the robot, and left/right is as viewed from behind the robot. - * - * How will you know if you have chosen the correct Orientation? With the correct orientation - * parameters selected, pitch/roll/yaw should act as follows: - * - * Pitch value should INCREASE as the robot is tipped UP at the front. (Rotation about X)
- * Roll value should INCREASE as the robot is tipped UP at the left side. (Rotation about Y)
- * Yaw value should INCREASE as the robot is rotated Counter Clockwise. (Rotation about Z)
- * - * The Yaw can be reset (to zero) by pressing the Y button on the gamepad (Triangle on a PS4 controller) - * - * The rotational velocities should follow the change in corresponding axes. - */ - -@TeleOp(name="Concept: IMU Orientation", group="Concept") -@Disabled -public class ConceptExploringIMUOrientation extends LinearOpMode { - static RevHubOrientationOnRobot.LogoFacingDirection[] logoFacingDirections - = RevHubOrientationOnRobot.LogoFacingDirection.values(); - static RevHubOrientationOnRobot.UsbFacingDirection[] usbFacingDirections - = RevHubOrientationOnRobot.UsbFacingDirection.values(); - static int LAST_DIRECTION = logoFacingDirections.length - 1; - static float TRIGGER_THRESHOLD = 0.2f; - - IMU imu; - int logoFacingDirectionPosition; - int usbFacingDirectionPosition; - boolean orientationIsValid = true; - - @Override public void runOpMode() throws InterruptedException { - imu = hardwareMap.get(IMU.class, "imu"); - logoFacingDirectionPosition = 0; // Up - usbFacingDirectionPosition = 2; // Forward - - updateOrientation(); - - boolean justChangedLogoDirection = false; - boolean justChangedUsbDirection = false; - - // Loop until stop requested - while (!isStopRequested()) { - - // Check to see if Yaw reset is requested (Y button) - if (gamepad1.y) { - telemetry.addData("Yaw", "Resetting\n"); - imu.resetYaw(); - } else { - telemetry.addData("Yaw", "Press Y (triangle) on Gamepad to reset.\n"); - } - - // Check to see if new Logo Direction is requested - if (gamepad1.left_bumper || gamepad1.right_bumper) { - if (!justChangedLogoDirection) { - justChangedLogoDirection = true; - if (gamepad1.left_bumper) { - logoFacingDirectionPosition--; - if (logoFacingDirectionPosition < 0) { - logoFacingDirectionPosition = LAST_DIRECTION; - } - } else { - logoFacingDirectionPosition++; - if (logoFacingDirectionPosition > LAST_DIRECTION) { - logoFacingDirectionPosition = 0; - } - } - updateOrientation(); - } - } else { - justChangedLogoDirection = false; - } - - // Check to see if new USB Direction is requested - if (gamepad1.left_trigger > TRIGGER_THRESHOLD || gamepad1.right_trigger > TRIGGER_THRESHOLD) { - if (!justChangedUsbDirection) { - justChangedUsbDirection = true; - if (gamepad1.left_trigger > TRIGGER_THRESHOLD) { - usbFacingDirectionPosition--; - if (usbFacingDirectionPosition < 0) { - usbFacingDirectionPosition = LAST_DIRECTION; - } - } else { - usbFacingDirectionPosition++; - if (usbFacingDirectionPosition > LAST_DIRECTION) { - usbFacingDirectionPosition = 0; - } - } - updateOrientation(); - } - } else { - justChangedUsbDirection = false; - } - - // Display User instructions and IMU data - telemetry.addData("logo Direction (set with bumpers)", logoFacingDirections[logoFacingDirectionPosition]); - telemetry.addData("usb Direction (set with triggers)", usbFacingDirections[usbFacingDirectionPosition] + "\n"); - - if (orientationIsValid) { - YawPitchRollAngles orientation = imu.getRobotYawPitchRollAngles(); - AngularVelocity angularVelocity = imu.getRobotAngularVelocity(AngleUnit.DEGREES); - - telemetry.addData("Yaw (Z)", "%.2f Deg. (Heading)", orientation.getYaw(AngleUnit.DEGREES)); - telemetry.addData("Pitch (X)", "%.2f Deg.", orientation.getPitch(AngleUnit.DEGREES)); - telemetry.addData("Roll (Y)", "%.2f Deg.\n", orientation.getRoll(AngleUnit.DEGREES)); - telemetry.addData("Yaw (Z) velocity", "%.2f Deg/Sec", angularVelocity.zRotationRate); - telemetry.addData("Pitch (X) velocity", "%.2f Deg/Sec", angularVelocity.xRotationRate); - telemetry.addData("Roll (Y) velocity", "%.2f Deg/Sec", angularVelocity.yRotationRate); - } else { - telemetry.addData("Error", "Selected orientation on robot is invalid"); - } - - telemetry.update(); - } - } - - // apply any requested orientation changes. - void updateOrientation() { - RevHubOrientationOnRobot.LogoFacingDirection logo = logoFacingDirections[logoFacingDirectionPosition]; - RevHubOrientationOnRobot.UsbFacingDirection usb = usbFacingDirections[usbFacingDirectionPosition]; - try { - RevHubOrientationOnRobot orientationOnRobot = new RevHubOrientationOnRobot(logo, usb); - imu.initialize(new IMU.Parameters(orientationOnRobot)); - orientationIsValid = true; - } catch (IllegalArgumentException e) { - orientationIsValid = false; - } - } -} diff --git a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptExternalHardwareClass.java b/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptExternalHardwareClass.java deleted file mode 100644 index 7a721fe..0000000 --- a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptExternalHardwareClass.java +++ /dev/null @@ -1,142 +0,0 @@ -/* Copyright (c) 2022 FIRST. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted (subject to the limitations in the disclaimer below) provided that - * the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list - * of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this - * list of conditions and the following disclaimer in the documentation and/or - * other materials provided with the distribution. - * - * Neither the name of FIRST nor the names of its contributors may be used to endorse or - * promote products derived from this software without specific prior written permission. - * - * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS - * LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.firstinspires.ftc.robotcontroller.external.samples; - -import com.qualcomm.robotcore.eventloop.opmode.Disabled; -import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode; -import com.qualcomm.robotcore.eventloop.opmode.TeleOp; -import com.qualcomm.robotcore.util.Range; - -/* - * This OpMode illustrates how to use an external "hardware" class to modularize all the robot's sensors and actuators. - * This approach is very efficient because the same hardware class can be used by all of your teleop and autonomous OpModes - * without requiring many copy & paste operations. Once you have defined and tested the hardware class with one OpMode, - * it is instantly available to other OpModes. - * - * The real benefit of this approach is that as you tweak your robot hardware, you only need to make changes in ONE place (the Hardware Class). - * So, to be effective you should put as much or your hardware setup and access code as possible in the hardware class. - * Essentially anything you do with hardware in BOTH Teleop and Auto should likely go in the hardware class. - * - * The Hardware Class is created in a separate file, and then an "instance" of this class is created in each OpMode. - * In order for the class to do typical OpMode things (like send telemetry data) it must be passed a reference to the - * OpMode object when it's created, so it can access all core OpMode functions. This is illustrated below. - * - * In this concept sample, the hardware class file is called RobotHardware.java and it must accompany this sample OpMode. - * So, if you copy ConceptExternalHardwareClass.java into TeamCode (using Android Studio or OnBotJava) then RobotHardware.java - * must also be copied to the same location (maintaining its name). - * - * For comparison purposes, this sample and its accompanying hardware class duplicates the functionality of the - * RobotTelopPOV_Linear OpMode. It assumes three motors (left_drive, right_drive and arm) and two servos (left_hand and right_hand) - * - * View the RobotHardware.java class file for more details - * - * Use Android Studio to Copy this Class, and Paste it into your team's code folder with a new name. - * Remove or comment out the @Disabled line to add this OpMode to the Driver Station OpMode list - * - * In OnBot Java, add a new OpMode, select this sample, and select TeleOp. - * Also add another new file named RobotHardware.java, select the sample with that name, and select Not an OpMode. - */ - -@TeleOp(name="Concept: Robot Hardware Class", group="Robot") -@Disabled -public class ConceptExternalHardwareClass extends LinearOpMode { - - // Create a RobotHardware object to be used to access robot hardware. - // Prefix any hardware functions with "robot." to access this class. - RobotHardware robot = new RobotHardware(this); - - @Override - public void runOpMode() { - double drive = 0; - double turn = 0; - double arm = 0; - double handOffset = 0; - - // initialize all the hardware, using the hardware class. See how clean and simple this is? - robot.init(); - - // Send telemetry message to signify robot waiting; - // Wait for the game to start (driver presses START) - waitForStart(); - - // run until the end of the match (driver presses STOP) - while (opModeIsActive()) { - - // Run wheels in POV mode (note: The joystick goes negative when pushed forward, so negate it) - // In this mode the Left stick moves the robot fwd and back, the Right stick turns left and right. - // This way it's also easy to just drive straight, or just turn. - drive = -gamepad1.left_stick_y; - turn = gamepad1.right_stick_x; - - // Combine drive and turn for blended motion. Use RobotHardware class - robot.driveRobot(drive, turn); - - // Use gamepad left & right Bumpers to open and close the claw - // Use the SERVO constants defined in RobotHardware class. - // Each time around the loop, the servos will move by a small amount. - // Limit the total offset to half of the full travel range - if (gamepad1.right_bumper) - handOffset += robot.HAND_SPEED; - else if (gamepad1.left_bumper) - handOffset -= robot.HAND_SPEED; - handOffset = Range.clip(handOffset, -0.5, 0.5); - - // Move both servos to new position. Use RobotHardware class - robot.setHandPositions(handOffset); - - // Use gamepad buttons to move arm up (Y) and down (A) - // Use the MOTOR constants defined in RobotHardware class. - if (gamepad1.y) - arm = robot.ARM_UP_POWER; - else if (gamepad1.a) - arm = robot.ARM_DOWN_POWER; - else - arm = 0; - - robot.setArmPower(arm); - - // Send telemetry messages to explain controls and show robot status - telemetry.addData("Drive", "Left Stick"); - telemetry.addData("Turn", "Right Stick"); - telemetry.addData("Arm Up/Down", "Y & A Buttons"); - telemetry.addData("Hand Open/Closed", "Left and Right Bumpers"); - telemetry.addData("-", "-------"); - - telemetry.addData("Drive Power", "%.2f", drive); - telemetry.addData("Turn Power", "%.2f", turn); - telemetry.addData("Arm Power", "%.2f", arm); - telemetry.addData("Hand Position", "Offset = %.2f", handOffset); - telemetry.update(); - - // Pace this loop so hands move at a reasonable speed. - sleep(50); - } - } -} diff --git a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptGamepadRumble.java b/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptGamepadRumble.java deleted file mode 100644 index cf846e1..0000000 --- a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptGamepadRumble.java +++ /dev/null @@ -1,200 +0,0 @@ -package org.firstinspires.ftc.robotcontroller.external.samples; - -import com.qualcomm.robotcore.eventloop.opmode.Disabled; -import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode; -import com.qualcomm.robotcore.eventloop.opmode.TeleOp; -import com.qualcomm.robotcore.hardware.Gamepad; -import com.qualcomm.robotcore.util.ElapsedTime; - -/* - * This OpMode illustrates using the rumble feature of many gamepads. - * - * Note: Some gamepads "rumble" better than others. - * The Xbox & PS4 have a left (rumble1) and right (rumble2) rumble motor. - * These two gamepads have two distinct rumble modes: Large on the left, and small on the right - * The Etpark gamepad may only respond to rumble1, and may only run at full power. - * The Logitech F310 gamepad does not have *any* rumble ability. - * - * Moral: You should use this sample to experiment with your specific gamepads to explore their rumble features. - * - * The rumble motors are accessed through the standard gamepad1 and gamepad2 objects. - * Several new methods were added to the Gamepad class in FTC SDK Rev 7 - * The key methods are as follows: - * - * .rumble(double rumble1, double rumble2, int durationMs) - * This method sets the rumble power of both motors for a specific time duration. - * Both rumble arguments are motor-power levels in the 0.0 to 1.0 range. - * durationMs is the desired length of the rumble action in milliseconds. - * This method returns immediately. - * Note: - * Use a durationMs of Gamepad.RUMBLE_DURATION_CONTINUOUS to provide a continuous rumble - * Use a power of 0, or duration of 0 to stop a rumble. - * - * .rumbleBlips(int count) allows an easy way to signal the driver with a series of rumble blips. - * Just specify how many blips you want. - * This method returns immediately. - * - * .runRumbleEffect(customRumbleEffect) allows you to run a custom rumble sequence that you have - * built using the Gamepad.RumbleEffect.Builder() - * A "custom effect" is a sequence of steps, where each step can rumble any of the - * rumble motors for a specific period at a specific power level. - * The Custom Effect will play as the (un-blocked) OpMode continues to run - * - * .isRumbling() returns true if there is a rumble effect in progress. - * Use this call to prevent stepping on a current rumble. - * - * .stopRumble() Stop any ongoing rumble or custom rumble effect. - * - * .rumble(int durationMs) Full power rumble for fixed duration. - * - * Note: Whenever a new Rumble command is issued, any currently executing rumble action will - * be truncated, and the new action started immediately. Take these precautions: - * 1) Do Not SPAM the rumble motors by issuing rapid fire commands - * 2) Multiple sources for rumble commands must coordinate to avoid tromping on each other. - * - * This can be achieved several possible ways: - * 1) Only having one source for rumble actions - * 2) Issuing rumble commands on transitions, rather than states. - * e.g. The moment a touch sensor is pressed, rather than the entire time it is being pressed. - * 3) Scheduling rumble commands based on timed events. e.g. 10 seconds prior to endgame - * 4) Rumble on non-overlapping mechanical actions. e.g. arm fully-extended or fully-retracted. - * 5) Use isRumbling() to hold off on a new rumble if one is already in progress. - * - * The examples shown here are representstive of how to invoke a gamepad rumble. - * It is assumed that these will be modified to suit the specific robot and team strategy needs. - * - * ######## Read the telemetry display on the Driver Station Screen for instructions. ###### - * - * Ex 1) This example shows a) how to create a custom rumble effect, and then b) how to trigger it based - * on game time. One use for this might be to alert the driver that half-time or End-game is approaching. - * - * Ex 2) This example shows tying the rumble power to a changing sensor value. - * In this case it is the Gamepad trigger, but it could be any sensor output scaled to the 0-1 range. - * Since it takes over the rumble motors, it is only performed when the Left Bumper is pressed. - * Note that this approach MUST include a way to turn OFF the rumble when the button is released. - * - * Ex 3) This example shows a simple way to trigger a 3-blip sequence. In this case it is - * triggered by the gamepad A (Cross) button, but it could be any sensor, like a touch or light sensor. - * Note that this code ensures that it only rumbles once when the input goes true. - * - * Ex 4) This example shows how to trigger a single rumble when an input value gets over a certain value. - * In this case it is reading the Right Trigger, but it could be any variable sensor, like a - * range sensor, or position sensor. The code needs to ensure that it is only triggered once, so - * it waits till the sensor drops back below the threshold before it can trigger again. - * - * Use Android Studio to Copy this Class, and Paste it into your team's code folder with a new name. - * Remove or comment out the @Disabled line to add this OpMode to the Driver Station OpMode list. - */ - -@Disabled -@TeleOp(name="Concept: Gamepad Rumble", group ="Concept") -public class ConceptGamepadRumble extends LinearOpMode -{ - boolean lastA = false; // Use to track the prior button state. - boolean lastLB = false; // Use to track the prior button state. - boolean highLevel = false; // used to prevent multiple level-based rumbles. - boolean secondHalf = false; // Use to prevent multiple half-time warning rumbles. - - Gamepad.RumbleEffect customRumbleEffect; // Use to build a custom rumble sequence. - ElapsedTime runtime = new ElapsedTime(); // Use to determine when end game is starting. - - final double HALF_TIME = 60.0; // Wait this many seconds before rumble-alert for half-time. - final double TRIGGER_THRESHOLD = 0.75; // Squeeze more than 3/4 to get rumble. - - @Override - public void runOpMode() - { - // Example 1. a) start by creating a three-pulse rumble sequence: right, LEFT, LEFT - customRumbleEffect = new Gamepad.RumbleEffect.Builder() - .addStep(0.0, 1.0, 500) // Rumble right motor 100% for 500 mSec - .addStep(0.0, 0.0, 300) // Pause for 300 mSec - .addStep(1.0, 0.0, 250) // Rumble left motor 100% for 250 mSec - .addStep(0.0, 0.0, 250) // Pause for 250 mSec - .addStep(1.0, 0.0, 250) // Rumble left motor 100% for 250 mSec - .build(); - - telemetry.addData(">", "Press Start"); - telemetry.update(); - - waitForStart(); - runtime.reset(); // Start game timer. - - // Loop while monitoring buttons for rumble triggers - while (opModeIsActive()) - { - // Read and save the current gamepad button states. - boolean currentA = gamepad1.a ; - boolean currentLB = gamepad1.left_bumper ; - - // Display the current Rumble status. Just for interest. - telemetry.addData(">", "Are we RUMBLING? %s\n", gamepad1.isRumbling() ? "YES" : "no" ); - - // ---------------------------------------------------------------------------------------- - // Example 1. b) Watch the runtime timer, and run the custom rumble when we hit half-time. - // Make sure we only signal once by setting "secondHalf" flag to prevent further rumbles. - // ---------------------------------------------------------------------------------------- - if ((runtime.seconds() > HALF_TIME) && !secondHalf) { - gamepad1.runRumbleEffect(customRumbleEffect); - secondHalf =true; - } - - // Display the time remaining while we are still counting down. - if (!secondHalf) { - telemetry.addData(">", "Halftime Alert Countdown: %3.0f Sec \n", (HALF_TIME - runtime.seconds()) ); - } - - - // ---------------------------------------------------------------------------------------- - // Example 2. If Left Bumper is being pressed, power the rumble motors based on the two trigger depressions. - // This is useful to see how the rumble feels at various power levels. - // ---------------------------------------------------------------------------------------- - if (currentLB) { - // Left Bumper is being pressed, so send left and right "trigger" values to left and right rumble motors. - gamepad1.rumble(gamepad1.left_trigger, gamepad1.right_trigger, Gamepad.RUMBLE_DURATION_CONTINUOUS); - - // Show what is being sent to rumbles - telemetry.addData(">", "Squeeze triggers to control rumbles"); - telemetry.addData("> : Rumble", "Left: %.0f%% Right: %.0f%%", gamepad1.left_trigger * 100, gamepad1.right_trigger * 100); - } else { - // Make sure rumble is turned off when Left Bumper is released (only one time each press) - if (lastLB) { - gamepad1.stopRumble(); - } - - // Prompt for manual rumble action - telemetry.addData(">", "Hold Left-Bumper to test Manual Rumble"); - telemetry.addData(">", "Press A (Cross) for three blips"); - telemetry.addData(">", "Squeeze right trigger slowly for 1 blip"); - } - lastLB = currentLB; // remember the current button state for next time around the loop - - - // ---------------------------------------------------------------------------------------- - // Example 3. Blip 3 times at the moment that A (Cross) is pressed. (look for pressed transition) - // BUT !!! Skip it altogether if the Gamepad is already rumbling. - // ---------------------------------------------------------------------------------------- - if (currentA && !lastA) { - if (!gamepad1.isRumbling()) // Check for possible overlap of rumbles. - gamepad1.rumbleBlips(3); - } - lastA = currentA; // remember the current button state for next time around the loop - - - // ---------------------------------------------------------------------------------------- - // Example 4. Rumble once when gamepad right trigger goes above the THRESHOLD. - // ---------------------------------------------------------------------------------------- - if (gamepad1.right_trigger > TRIGGER_THRESHOLD) { - if (!highLevel) { - gamepad1.rumble(0.9, 0, 200); // 200 mSec burst on left motor. - highLevel = true; // Hold off any more triggers - } - } else { - highLevel = false; // We can trigger again now. - } - - // Send the telemetry data to the Driver Station, and then pause to pace the program. - telemetry.update(); - sleep(10); - } - } -} diff --git a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptGamepadTouchpad.java b/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptGamepadTouchpad.java deleted file mode 100644 index 84d8cec..0000000 --- a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptGamepadTouchpad.java +++ /dev/null @@ -1,77 +0,0 @@ -package org.firstinspires.ftc.robotcontroller.external.samples; - -import com.qualcomm.robotcore.eventloop.opmode.Disabled; -import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode; -import com.qualcomm.robotcore.eventloop.opmode.TeleOp; -import org.firstinspires.ftc.robotcore.external.Telemetry; - -/* - * This OpMode illustrates using the touchpad feature found on some gamepads. - * - * The Sony PS4 gamepad can detect two distinct touches on the central touchpad. - * Other gamepads with different touchpads may provide mixed results. - * - * The touchpads are accessed through the standard gamepad1 and gamepad2 objects. - * Several new members were added to the Gamepad class in FTC SDK Rev 7 - * - * .touchpad_finger_1 returns true if at least one finger is detected. - * .touchpad_finger_1_x finger 1 X coordinate. Valid if touchpad_finger_1 is true - * .touchpad_finger_1_y finger 1 Y coordinate. Valid if touchpad_finger_1 is true - * - * .touchpad_finger_2 returns true if a second finger is detected - * .touchpad_finger_2_x finger 2 X coordinate. Valid if touchpad_finger_2 is true - * .touchpad_finger_2_y finger 2 Y coordinate. Valid if touchpad_finger_2 is true - * - * Finger touches are reported with an X and Y coordinate in following coordinate system. - * - * 1) X is the Horizontal axis, and Y is the vertical axis - * 2) The 0,0 origin is at the center of the touchpad. - * 3) 1.0, 1.0 is at the top right corner of the touchpad. - * 4) -1.0,-1.0 is at the bottom left corner of the touchpad. - * - * Use Android Studio to Copy this Class, and Paste it into your team's code folder with a new name. - * Remove or comment out the @Disabled line to add this OpMode to the Driver Station OpMode list. - */ - -@Disabled -@TeleOp(name="Concept: Gamepad Touchpad", group ="Concept") -public class ConceptGamepadTouchpad extends LinearOpMode -{ - @Override - public void runOpMode() - { - telemetry.setDisplayFormat(Telemetry.DisplayFormat.MONOSPACE); - - telemetry.addData(">", "Press Start"); - telemetry.update(); - - waitForStart(); - - while (opModeIsActive()) - { - boolean finger = false; - - // Display finger 1 x & y position if finger detected - if(gamepad1.touchpad_finger_1) - { - finger = true; - telemetry.addLine(String.format("Finger 1: x=%5.2f y=%5.2f\n", gamepad1.touchpad_finger_1_x, gamepad1.touchpad_finger_1_y)); - } - - // Display finger 2 x & y position if finger detected - if(gamepad1.touchpad_finger_2) - { - finger = true; - telemetry.addLine(String.format("Finger 2: x=%5.2f y=%5.2f\n", gamepad1.touchpad_finger_2_x, gamepad1.touchpad_finger_2_y)); - } - - if(!finger) - { - telemetry.addLine("No fingers"); - } - - telemetry.update(); - sleep(10); - } - } -} diff --git a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptLEDStick.java b/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptLEDStick.java deleted file mode 100644 index 01729bb..0000000 --- a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptLEDStick.java +++ /dev/null @@ -1,123 +0,0 @@ -package org.firstinspires.ftc.robotcontroller.external.samples; -/* - Copyright (c) 2021-24 Alan Smith - - All rights reserved. - - Redistribution and use in source and binary forms, with or without modification, - are permitted (subject to the limitations in the disclaimer below) provided that - the following conditions are met: - - Redistributions of source code must retain the above copyright notice, this list - of conditions and the following disclaimer. - - Redistributions in binary form must reproduce the above copyright notice, this - list of conditions and the following disclaimer in the documentation and/or - other materials provided with the distribution. - - Neither the name of Alan Smith nor the names of its contributors may be used to - endorse or promote products derived from this software without specific prior - written permission. - - NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS - LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESSFOR A PARTICULAR PURPOSE - ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR - TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -import android.graphics.Color; - -import com.qualcomm.hardware.sparkfun.SparkFunLEDStick; -import com.qualcomm.robotcore.eventloop.opmode.Disabled; -import com.qualcomm.robotcore.eventloop.opmode.OpMode; -import com.qualcomm.robotcore.eventloop.opmode.TeleOp; -import com.qualcomm.robotcore.util.Range; - -/* - * This OpMode illustrates how to use the SparkFun QWIIC LED Strip - * - * This is a simple way to add a strip of 10 LEDs to your robot where you can set the color of each - * LED or the whole strip. This allows for driver feedback or even just fun ways to show your team - * colors. - * - * Why? - * Because more LEDs == more fun!! - * - * This OpMode assumes that the QWIIC LED Stick is attached to an I2C interface named "back_leds" in the robot configuration. - * - * Use Android Studio to Copy this Class, and Paste it into your team's code folder with a new name. - * Remove or comment out the @Disabled line to add this OpMode to the Driver Station OpMode list - * - * You can buy this product here: https://www.sparkfun.com/products/18354 - * Don't forget to also buy this to make it easy to connect to your Control or Expansion Hub: - * https://www.sparkfun.com/products/25596 - */ -@TeleOp(name = "Concept: LED Stick", group = "Concept") -@Disabled -public class ConceptLEDStick extends OpMode { - private boolean wasUp; - private boolean wasDown; - private int brightness = 5; // this needs to be between 0 and 31 - private final static double END_GAME_TIME = 120 - 30; - - private SparkFunLEDStick ledStick; - - @Override - public void init() { - ledStick = hardwareMap.get(SparkFunLEDStick.class, "back_leds"); - ledStick.setBrightness(brightness); - ledStick.setColor(Color.GREEN); - } - - @Override - public void start() { - resetRuntime(); - } - - @Override - public void loop() { - telemetry.addLine("Hold the A button to turn blue"); - telemetry.addLine("Hold the B button to turn red"); - telemetry.addLine("Hold the left bumper to turn off"); - telemetry.addLine("Use DPAD Up/Down to change brightness"); - - if (getRuntime() > END_GAME_TIME) { - int[] ledColors = {Color.RED, Color.YELLOW, Color.RED, Color.YELLOW, Color.RED, - Color.YELLOW, Color.RED, Color.YELLOW, Color.RED, Color.YELLOW}; - ledStick.setColors(ledColors); - } else if (gamepad1.a) { - ledStick.setColor(Color.BLUE); - } else if (gamepad1.b) { - ledStick.setColor(Color.RED); - } else if (gamepad1.left_bumper) { - ledStick.turnAllOff(); - } else { - ledStick.setColor(Color.GREEN); - } - - /* - * Use DPAD up and down to change brightness - */ - int newBrightness = brightness; - if (gamepad1.dpad_up && !wasUp) { - newBrightness = brightness + 1; - } else if (gamepad1.dpad_down && !wasDown) { - newBrightness = brightness - 1; - } - if (newBrightness != brightness) { - brightness = Range.clip(newBrightness, 0, 31); - ledStick.setBrightness(brightness); - } - telemetry.addData("Brightness", brightness); - - wasDown = gamepad1.dpad_down; - wasUp = gamepad1.dpad_up; - } -} diff --git a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptMotorBulkRead.java b/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptMotorBulkRead.java deleted file mode 100644 index 5439f78..0000000 --- a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptMotorBulkRead.java +++ /dev/null @@ -1,227 +0,0 @@ -/* Copyright (c) 2019 Phil Malone. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted (subject to the limitations in the disclaimer below) provided that - * the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list - * of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this - * list of conditions and the following disclaimer in the documentation and/or - * other materials provided with the distribution. - * - * Neither the name of FIRST nor the names of its contributors may be used to endorse or - * promote products derived from this software without specific prior written permission. - * - * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS - * LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.firstinspires.ftc.robotcontroller.external.samples; - -import com.qualcomm.hardware.lynx.LynxModule; -import com.qualcomm.robotcore.eventloop.opmode.Disabled; -import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode; -import com.qualcomm.robotcore.eventloop.opmode.TeleOp; -import com.qualcomm.robotcore.hardware.DcMotorEx; -import com.qualcomm.robotcore.util.ElapsedTime; - -import java.util.List; - -/* - * This OpMode illustrates how to use the Expansion Hub's Bulk-Read feature to speed up control cycle times. - * In this example there are 4 motors that need their encoder positions, and velocities read. - * The sample is written to work with one or two expansion hubs, with no assumption as to where the motors are located. - * - * Three scenarios are tested: - * Cache Mode = OFF This is the normal default, where no cache is used, and every read produces a discrete transaction with - * an expansion hub, which is the slowest approach, but guarentees that the value is as fresh (recent) as possible.. - * - * Cache Mode = AUTO This mode will attempt to minimize the number of discrete read commands, by performing bulk-reads - * and then returning values that have been cached. The cache is updated automatically whenever any specific encoder is re-read. - * This mode will always return new data, but it may perform more bulk-reads than absolutely required. - * Extra reads will be performed if multiple encoder/velocity reads are performed on the same encoder in one control cycle. - * This mode is a good compromise between the OFF and MANUAL modes. - * Note: If there are significant user-program delays between encoder reads, the cached value may not be fresh (recent). - * You can issue a clearBulkCache() call at any time force a fresh bulk-read on the next encoder read. - * - * Cache Mode = MANUAL This mode requires the user's code to determine the best time to clear the cached bulk-read data. - * Well organized code will reset the cache once at the beginning of the control cycle, and then immediately read and store all the encoder values. - * This approach will produce the shortest cycle times, but it does require the user to manually clear the cache. - * Since NO automatic Bulk-Reads are performed, neglecting to clear the bulk cache will result in the same values being returned - * each time an encoder read is performed. - * - * ------------------------------------- - * - * General tip to speed up your control cycles: - * - * No matter what method you use to read encoders and other inputs, you should try to - * avoid reading the same encoder input multiple times around a control loop. - * Under normal conditions, this will slow down the control loop. - * The preferred method is to read all the required inputs ONCE at the beginning of the loop, - * and save the values in variable that can be used by other parts of the control code. - * - * eg: if you are sending encoder positions to your telemetry display, putting a getCurrentPosition() - * call in the telemetry statement will force the code to go and get another copy which will take time. - * It's much better read the position into a variable once, and use that variable for control AND display. - * Reading saved variables takes no time at all. - * - * Once you put all your sensor reads at the beginning of the control cycle, it's very easy to use - * the bulk-read AUTO mode to streamline your cycle timing. - */ -@TeleOp (name = "Motor Bulk Reads", group = "Tests") -@Disabled -public class ConceptMotorBulkRead extends LinearOpMode { - - final int TEST_CYCLES = 500; // Number of control cycles to run to determine cycle times. - - private DcMotorEx m1, m2, m3, m4; // Motor Objects - private long e1, e2, e3, e4; // Encoder Values - private double v1, v2, v3, v4; // Velocities - - // Cycle Times - double t1 = 0; - double t2 = 0; - double t3 = 0; - - @Override - public void runOpMode() { - - int cycles; - - // Important Step 1: Make sure you use DcMotorEx when you instantiate your motors. - m1 = hardwareMap.get(DcMotorEx.class, "m1"); // Configure the robot to use these 4 motor names, - m2 = hardwareMap.get(DcMotorEx.class, "m2"); // or change these strings to match your existing Robot Configuration. - m3 = hardwareMap.get(DcMotorEx.class, "m3"); - m4 = hardwareMap.get(DcMotorEx.class, "m4"); - - // Important Step 2: Get access to a list of Expansion Hub Modules to enable changing caching methods. - List allHubs = hardwareMap.getAll(LynxModule.class); - - ElapsedTime timer = new ElapsedTime(); - - telemetry.addData(">", "Press START to start tests"); - telemetry.addData(">", "Test results will update for each access method."); - telemetry.update(); - waitForStart(); - - // -------------------------------------------------------------------------------------- - // Run control loop using legacy encoder reads - // In this mode, a single read is done for each encoder position, and a bulk read is done for each velocity read. - // This is the worst case scenario. - // This is the same as using LynxModule.BulkCachingMode.OFF - // -------------------------------------------------------------------------------------- - - displayCycleTimes("Test 1 of 3 (Wait for completion)"); - - timer.reset(); - cycles = 0; - while (opModeIsActive() && (cycles++ < TEST_CYCLES)) { - e1 = m1.getCurrentPosition(); - e2 = m2.getCurrentPosition(); - e3 = m3.getCurrentPosition(); - e4 = m4.getCurrentPosition(); - - v1 = m1.getVelocity(); - v2 = m2.getVelocity(); - v3 = m3.getVelocity(); - v4 = m4.getVelocity(); - - // Put Control loop action code here. - - } - // calculate the average cycle time. - t1 = timer.milliseconds() / cycles; - displayCycleTimes("Test 2 of 3 (Wait for completion)"); - - // -------------------------------------------------------------------------------------- - // Run test cycles using AUTO cache mode - // In this mode, only one bulk read is done per cycle, UNLESS you read a specific encoder/velocity item AGAIN in that cycle. - // -------------------------------------------------------------------------------------- - - // Important Step 3: Option A. Set all Expansion hubs to use the AUTO Bulk Caching mode - for (LynxModule module : allHubs) { - module.setBulkCachingMode(LynxModule.BulkCachingMode.AUTO); - } - - timer.reset(); - cycles = 0; - while (opModeIsActive() && (cycles++ < TEST_CYCLES)) { - e1 = m1.getCurrentPosition(); // Uses 1 bulk-read for all 4 encoder/velocity reads, - e2 = m2.getCurrentPosition(); // but don't do any `get` operations more than once per cycle. - e3 = m3.getCurrentPosition(); - e4 = m4.getCurrentPosition(); - - v1 = m1.getVelocity(); - v2 = m2.getVelocity(); - v3 = m3.getVelocity(); - v4 = m4.getVelocity(); - - // Put Control loop action code here. - - } - // calculate the average cycle time. - t2 = timer.milliseconds() / cycles; - displayCycleTimes("Test 3 of 3 (Wait for completion)"); - - // -------------------------------------------------------------------------------------- - // Run test cycles using MANUAL cache mode - // In this mode, only one block read is done each control cycle. - // This is the MOST efficient method, but it does require that the cache is cleared manually each control cycle. - // -------------------------------------------------------------------------------------- - - // Important Step 3: Option B. Set all Expansion hubs to use the MANUAL Bulk Caching mode - for (LynxModule module : allHubs) { - module.setBulkCachingMode(LynxModule.BulkCachingMode.MANUAL); - } - - timer.reset(); - cycles = 0; - while (opModeIsActive() && (cycles++ < TEST_CYCLES)) { - - // Important Step 4: If you are using MANUAL mode, you must clear the BulkCache once per control cycle - for (LynxModule module : allHubs) { - module.clearBulkCache(); - } - - e1 = m1.getCurrentPosition(); // Uses 1 bulk-read to obtain ALL the motor data - e2 = m2.getCurrentPosition(); // There is no penalty for doing more `get` operations in this cycle, - e3 = m3.getCurrentPosition(); // but they will return the same data. - e4 = m4.getCurrentPosition(); - - v1 = m1.getVelocity(); - v2 = m2.getVelocity(); - v3 = m3.getVelocity(); - v4 = m4.getVelocity(); - - // Put Control loop action code here. - - } - // calculate the average cycle time. - t3 = timer.milliseconds() / cycles; - displayCycleTimes("Complete"); - - // wait until op-mode is stopped by user, before clearing display. - while (opModeIsActive()) ; - } - - // Display three comparison times. - void displayCycleTimes(String status) { - telemetry.addData("Testing", status); - telemetry.addData("Cache = OFF", "%5.1f mS/cycle", t1); - telemetry.addData("Cache = AUTO", "%5.1f mS/cycle", t2); - telemetry.addData("Cache = MANUAL", "%5.1f mS/cycle", t3); - telemetry.update(); - } -} - diff --git a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptNullOp.java b/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptNullOp.java deleted file mode 100644 index 4a887bd..0000000 --- a/library/FtcRobotController/src/main/java/org/firstinspires/ftc/robotcontroller/external/samples/ConceptNullOp.java +++ /dev/null @@ -1,89 +0,0 @@ -/* Copyright (c) 2017 FIRST. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted (subject to the limitations in the disclaimer below) provided that - * the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list - * of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this - * list of conditions and the following disclaimer in the documentation and/or - * other materials provided with the distribution. - * - * Neither the name of FIRST nor the names of its contributors may be used to endorse or - * promote products derived from this software without specific prior written permission. - * - * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS - * LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.firstinspires.ftc.robotcontroller.external.samples; - -import com.qualcomm.robotcore.eventloop.opmode.Disabled; -import com.qualcomm.robotcore.eventloop.opmode.OpMode; -import com.qualcomm.robotcore.eventloop.opmode.TeleOp; -import com.qualcomm.robotcore.util.ElapsedTime; - -/* - * Demonstrates an empty iterative OpMode - */ -@TeleOp(name = "Concept: NullOp", group = "Concept") -@Disabled -public class ConceptNullOp extends OpMode { - - private ElapsedTime runtime = new ElapsedTime(); - - /** - * This method will be called once, when the INIT button is pressed. - */ - @Override - public void init() { - telemetry.addData("Status", "Initialized"); - } - - /** - * This method will be called repeatedly during the period between when - * the INIT button is pressed and when the START button is pressed (or the - * OpMode is stopped). - */ - @Override - public void init_loop() { - } - - /** - * This method will be called once, when the START button is pressed. - */ - @Override - public void start() { - runtime.reset(); - } - - /** - * This method will be called repeatedly during the period between when - * the START button is pressed and when the OpMode is stopped. - */ - @Override - public void loop() { - telemetry.addData("Status", "Run Time: " + runtime.toString()); - } - - /** - * This method will be called once, when this OpMode is stopped. - *