Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion TeamCode/src/main/kotlin/pioneer/Constants.kt
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ object Constants {
}

object TransferData {
var allianceColor = AllianceColor.NEUTRAL
var allianceColor = AllianceColor.NONE
var turretPositionTicks = 0
var spindexerPositionTicks = 0
}
Expand Down
2 changes: 1 addition & 1 deletion TeamCode/src/main/kotlin/pioneer/decode/GoalTag.kt
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ object GoalTagProcessor {
?.let { GoalTag.RED }
}

AllianceColor.NEUTRAL -> {
AllianceColor.NONE -> {
null
}
}
Expand Down
2 changes: 1 addition & 1 deletion TeamCode/src/main/kotlin/pioneer/decode/Obelisk.kt
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ object Obelisk {
when (alliance) {
AllianceColor.BLUE -> validTags.maxByOrNull { it.ftcPose.x }?.id
AllianceColor.RED -> validTags.minByOrNull { it.ftcPose.x }?.id
AllianceColor.NEUTRAL -> null
AllianceColor.NONE -> null
}
return motifTagId?.let { Motif(it) }
}
Expand Down
2 changes: 1 addition & 1 deletion TeamCode/src/main/kotlin/pioneer/decode/Points.kt
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ class Points(
when (c) {
AllianceColor.RED -> this
AllianceColor.BLUE -> Pose(-this.x, this.y, -this.theta)
AllianceColor.NEUTRAL -> this
AllianceColor.NONE -> this
}

// Key positions on the field
Expand Down
2 changes: 1 addition & 1 deletion TeamCode/src/main/kotlin/pioneer/general/AllianceColor.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@ package pioneer.general
enum class AllianceColor {
RED,
BLUE,
NEUTRAL,
NONE,
}
7 changes: 7 additions & 0 deletions TeamCode/src/main/kotlin/pioneer/general/Period.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package pioneer.general

enum class Period {
AUTO,
TELEOP,
NONE,
}
36 changes: 36 additions & 0 deletions TeamCode/src/main/kotlin/pioneer/helpers/OpModeDataTransfer.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package pioneer.helpers

import com.google.gson.Gson
import org.firstinspires.ftc.robotcore.internal.system.AppUtil
import java.io.File
import pioneer.Bot


object OpModeDataTransfer {
data class OMDT(
val bot: Bot? = null,
var timestamp: Long = System.currentTimeMillis(),
val data: MutableMap<String, Any?> = mutableMapOf()
)

private val gson = Gson()
private val file: File by lazy {
AppUtil.getInstance().getSettingsFile("omdt.json").apply {
parentFile?.mkdirs()
}
}

fun save(value: OMDT) {
file.writeText(gson.toJson(value))
}

fun loadOrNull(): OMDT? = try {
if (file.exists()) gson.fromJson(file.readText(), OMDT::class.java)
else null
} catch (e: Exception) {
null
}

// Deletes the OMDT file. Safe to call even if the file doesn't exist.
fun clear() = file.delete()
}
57 changes: 48 additions & 9 deletions TeamCode/src/main/kotlin/pioneer/opmodes/BaseOpMode.kt
Original file line number Diff line number Diff line change
@@ -1,25 +1,32 @@
package pioneer.opmodes

import com.acmerobotics.dashboard.telemetry.TelemetryPacket
import com.acmerobotics.dashboard.FtcDashboard
import com.qualcomm.robotcore.eventloop.opmode.OpMode
import com.qualcomm.robotcore.eventloop.opmode.TeleOp
import pioneer.Bot
import pioneer.hardware.MecanumBase
import pioneer.localization.localizers.Pinpoint
import pioneer.helpers.Chrono
import pioneer.helpers.FileLogger
import pioneer.localization.localizers.Pinpoint
import pioneer.general.Period
import pioneer.general.AllianceColor
import pioneer.helpers.OpModeDataTransfer

// Base OpMode class to be extended by all user-defined OpModes
abstract class BaseOpMode : OpMode() {
// Bot instance to be defined in subclasses
abstract class BaseOpMode(
private val period: Period = Period.NONE,
private val allianceColor: AllianceColor = AllianceColor.NONE
) : OpMode() {
// Bot instance
protected lateinit var bot: Bot

// Telemetry packet for dashboard
protected var telemetryPacket = TelemetryPacket()

// Dashboard instance
private val dashboard =
com.acmerobotics.dashboard.FtcDashboard
.getInstance()
FtcDashboard.getInstance()

// Tracker and getter for dt
protected val chrono = Chrono()
Expand All @@ -30,11 +37,29 @@ abstract class BaseOpMode : OpMode() {
get() = getRuntime()

final override fun init() {
onInit() // Call user-defined init method
bot.initAll() // Initialize bot hardware
// Auto-load bot in TELEOP period
if (period == Period.TELEOP) {
OpModeDataTransfer.loadOrNull()?.let { omdt ->
// Reinitialize bot from saved object
omdt.bot?.let { savedBot ->
bot = savedBot
// bot.initAll() // Re-initialize hardware (TODO: needs testing)
}
}
}

// If bot wasn't loaded from OMDT, call user init
if (!::bot.isInitialized) {
throw IllegalStateException("Bot not initialized. Please set 'bot' in onInit().")
onInit() // Call user-defined init method
if (!::bot.isInitialized) {
throw IllegalStateException("Bot not initialized. Please set 'bot' in onInit().")
}
bot.initAll() // Initialize bot hardware
} else {
// Bot was loaded, but still call onInit for any additional setup
onInit()
}

updateTelemetry()
}

Expand All @@ -59,6 +84,20 @@ abstract class BaseOpMode : OpMode() {
bot.mecanumBase?.stop() // Ensure motors are stopped
FileLogger.flush() // Flush any logged data
onStop() // Call user-defined stop method

// Auto-save data in AUTO period, clear in TELEOP
when (period) {
Period.AUTO -> {
val omdt = OpModeDataTransfer.OMDT(
bot = bot,
)
OpModeDataTransfer.save(omdt)
}
Period.TELEOP -> {
OpModeDataTransfer.clear()
}
else -> { /* No data transfer for NONE */ }
}
}

private fun updateTelemetry() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package pioneer.opmodes.other.testing

import com.qualcomm.robotcore.eventloop.opmode.Autonomous
import pioneer.Bot
import pioneer.BotType
import pioneer.general.AllianceColor
import pioneer.general.Period
import pioneer.opmodes.BaseOpMode

@Autonomous(name = "Test Auto Data Transfer", group = "Testing")
class TestAutoDataTransfer : BaseOpMode(
period = Period.AUTO,
allianceColor = AllianceColor.RED
) {

override fun onInit() {
bot = Bot.fromType(BotType.GOBILDA_STARTER_BOT, hardwareMap)
telemetry.addLine("Test Auto - Will save bot for teleop")
telemetry.addLine("Press START to run")
telemetry.update()
}

override fun onLoop() {
val currentPose = bot.pinpoint?.pose

telemetry.addLine("✓ Bot will auto-save on stop!")
telemetry.addLine()
telemetry.addData("Alliance", "RED")
telemetry.addData("Bot Type", bot.type)
if (currentPose != null) {
telemetry.addData("Current Pose", "x=%.1f, y=%.1f, θ=%.1f°",
currentPose.x, currentPose.y, currentPose.theta)
}
telemetry.addLine()
telemetry.addLine("Stop OpMode to save bot, then run Test Teleop")
telemetry.update()
}

override fun onStop() {
telemetry.addLine("Auto stopped - Bot saved by BaseOpMode")
telemetry.update()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package pioneer.opmodes.other.testing

import com.qualcomm.robotcore.eventloop.opmode.TeleOp
import pioneer.Bot
import pioneer.BotType
import pioneer.general.AllianceColor
import pioneer.general.Period
import pioneer.helpers.Pose
import pioneer.opmodes.BaseOpMode

@TeleOp(name = "Test Teleop Data Transfer", group = "Testing")
class TestTeleopDataTransfer : BaseOpMode(
period = Period.TELEOP,
allianceColor = AllianceColor.RED
) {

override fun onInit() {
// Bot is already loaded by BaseOpMode if it exists from AUTO
// Otherwise, we need to create a new one
// We can check by looking at bot.type or just always show status

telemetry.addLine("=== BOT INITIALIZATION ===")
telemetry.addLine("Bot loaded - Check if from AUTO or new")
telemetry.addData("Bot Type", bot.type)
bot.pinpoint?.pose?.let { pose ->
telemetry.addData("Starting Pose", "x=%.1f, y=%.1f, θ=%.1f°".format(
pose.x, pose.y, pose.theta
))
}
telemetry.addLine()
telemetry.addLine("If this is a default pose, bot was created new")
telemetry.addLine("If this is your AUTO end pose, bot was loaded!")
telemetry.addLine()
telemetry.addLine("Use left stick to drive")
telemetry.update()
}

override fun onLoop() {
// Simple drive controls for testing
drive()

// Display bot status
telemetry.addLine("=== BOT STATUS ===")
telemetry.addData("Bot Type", bot.type)

bot.pinpoint?.let { pinpoint ->
telemetry.addLine()
telemetry.addLine("--- CURRENT STATE ---")
telemetry.addData("Current Pose", "x=%.1f, y=%.1f, θ=%.1f°",
pinpoint.pose.x, pinpoint.pose.y, pinpoint.pose.theta)
}

telemetry.addLine()
telemetry.addLine("Drive: Left stick | Turn: Right stick X")
telemetry.update()
}

private fun drive() {
val direction = Pose(
gamepad1.left_stick_x.toDouble(),
-gamepad1.left_stick_y.toDouble()
)
bot.mecanumBase?.setDrivePower(
Pose(
vx = direction.x,
vy = direction.y,
omega = gamepad1.right_stick_x.toDouble(),
),
0.5, // Default power
1000.0, // Max velocity
)
}
}
Loading