diff --git a/chrono/src/main/java/com/blazedeveloper/chrono/structure/LogTable.kt b/chrono/src/main/java/com/blazedeveloper/chrono/structure/LogTable.kt index af89d63..1bfd08e 100644 --- a/chrono/src/main/java/com/blazedeveloper/chrono/structure/LogTable.kt +++ b/chrono/src/main/java/com/blazedeveloper/chrono/structure/LogTable.kt @@ -92,9 +92,9 @@ class LogTable @JvmOverloads constructor( fun > put(key: String, value: Array) = put(key, value.map { it.name }.toTypedArray()) - /** Puts a normalized color represented by a float array (ARGB order) into the table at a specified key */ + /** Puts a normalized color represented by a float array (RGBA order) into the table at a specified key */ fun put(key: String, value: NormalizedRGBA) = - put(key, floatArrayOf(value.alpha, value.red, value.green, value.blue)) + put(key, floatArrayOf(value.red, value.green, value.blue, value.alpha)) /** * Gets a raw LogValue from the table at the specified [key]. @@ -232,12 +232,12 @@ class LogTable @JvmOverloads constructor( * the [default] is returned. */ fun get(key: String, default: NormalizedRGBA): NormalizedRGBA { - val array = get(key, floatArrayOf(default.alpha, default.red, default.green, default.blue)) + val array = get(key, floatArrayOf(default.red, default.green, default.blue, default.alpha)) return NormalizedRGBA().apply { - alpha = array[0] - red = array[1] - green = array[2] - blue = array[3] + red = array[0] + green = array[1] + blue = array[2] + alpha = array[3] } } } \ No newline at end of file diff --git a/chrono/src/main/java/com/blazedeveloper/chrono/structure/LoggableInputs.kt b/chrono/src/main/java/com/blazedeveloper/chrono/structure/LoggableInputs.kt index ed97528..a66cd85 100644 --- a/chrono/src/main/java/com/blazedeveloper/chrono/structure/LoggableInputs.kt +++ b/chrono/src/main/java/com/blazedeveloper/chrono/structure/LoggableInputs.kt @@ -1,6 +1,51 @@ package com.blazedeveloper.chrono.structure +import com.qualcomm.robotcore.hardware.NormalizedRGBA +import kotlin.reflect.KProperty + interface LoggableInputs { fun toLog(table: LogTable) fun fromLog(table: LogTable) +} + +abstract class AutoLoggableInputs : LoggableInputs { + inner class Field( + val key: String, + var value: T, + val toLog: LogTable.(String, T) -> Unit, + val fromLog: LogTable.(String, T) -> T + ) { + operator fun setValue(thisRef: Any, property: KProperty<*>, newValue: T) { value = newValue } + operator fun getValue(thisRef: Any, property: KProperty<*>) = value + + operator fun provideDelegate(thisRef: Any, property: KProperty<*>): Field { + toLogs.add { it.toLog(key, value) } + fromLogs.add { it.fromLog(key, value) } + return this + } + } + + fun logged(key: String, value: LogValue) = Field(key, value, LogTable::put, LogTable::get) + fun logged(key: String, value: ByteArray) = Field(key, value, LogTable::put, LogTable::get) + fun logged(key: String, value: Boolean) = Field(key, value, LogTable::put, LogTable::get) + fun logged(key: String, value: Int) = Field(key, value, LogTable::put, LogTable::get) + fun logged(key: String, value: Long) = Field(key, value, LogTable::put, LogTable::get) + fun logged(key: String, value: Float) = Field(key, value, LogTable::put, LogTable::get) + fun logged(key: String, value: Double) = Field(key, value, LogTable::put, LogTable::get) + fun logged(key: String, value: String) = Field(key, value, LogTable::put, LogTable::get) + fun logged(key: String, value: BooleanArray) = Field(key, value, LogTable::put, LogTable::get) + fun logged(key: String, value: IntArray) = Field(key, value, LogTable::put, LogTable::get) + fun logged(key: String, value: LongArray) = Field(key, value, LogTable::put, LogTable::get) + fun logged(key: String, value: FloatArray) = Field(key, value, LogTable::put, LogTable::get) + fun logged(key: String, value: DoubleArray) = Field(key, value, LogTable::put, LogTable::get) + fun logged(key: String, value: Array) = Field(key, value, LogTable::put, LogTable::get) + inline fun > logged(key: String, value: E) = Field(key, value, LogTable::put, LogTable::get) + inline fun > logged(key: String, value: Array) = Field(key, value, LogTable::put, LogTable::get) + fun logged(key: String, value: NormalizedRGBA) = Field(key, value, LogTable::put, LogTable::get) + + val toLogs = mutableListOf<(LogTable) -> Unit>() + val fromLogs = mutableListOf<(LogTable) -> Unit>() + + final override fun toLog(table: LogTable) = toLogs.forEach { it(table) } + final override fun fromLog(table: LogTable) = fromLogs.forEach { it(table) } } \ No newline at end of file diff --git a/docs/docs/functionality/data-types.md b/docs/docs/functionality/data-types.md index 8d57c88..2c305f0 100644 --- a/docs/docs/functionality/data-types.md +++ b/docs/docs/functionality/data-types.md @@ -21,4 +21,12 @@ Chrono supports these simple types and their arrays: ### Enums Chrono also supports logging and replaying generic enum values and enum arrays. -They are represented by string values from the enum's ``name()`` method. \ No newline at end of file +They are represented by string values from the enum's ``name()`` method. + +### Normalized Colors +Chrono also supports logging and replaying ``NormalizedRGBA`` objects from color sensors. +These are represented by float arrays, storing the colors components in order: + - Red Channel + - Green Channel + - Blue Channel + - Alpha / Opacity diff --git a/docs/docs/functionality/inputs.md b/docs/docs/functionality/inputs.md new file mode 100644 index 0000000..c91be5e --- /dev/null +++ b/docs/docs/functionality/inputs.md @@ -0,0 +1,125 @@ +--- +title: "Input Logging" +--- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +Input logging in Chrono is nearly identical to AdvantageKit. +All robot input data, (sensors, encoders, vision, etc.) must be logged in order to accurately +recreate a log file's conditions in log replay. + +A common structure to ensure this is *hardware abstraction*, that is, separating hardware and +subsystem logic to maximize flexibility and pipe input data through a common place. This separation +is called an *IO interface*. + +:::tip +For those not familiar with AdvantageKit, it is highly recommended that you check out the +[AdvantageKit documentation](https://docs.advantagekit.org/data-flow/recording-inputs/io-interfaces) for more information on this structure. This documentation +will mainly emphasize how Chrono implements the theory that pre-exists and is documented +in AdvantageKit. +::: + +## Input Schemas +Input data should flow through ``LoggableInput`` objects, which define the structure of input +to the subsystem implement methods to save and load data from a log file. Subsystems then reference +these object rather than directly querying hardware. + +Examples of defining these input objects are as follows: + + + + +```kotlin +class DriveInputs : LoggableInputs { + var yawRads = 0.0 + + var flPos = 0.0 + var frPos = 0.0 + var blPos = 0.0 + var brPos = 0.0 + + override fun toLog(table: LogTable) { + table.put("YawRads", yawRads) + table.put("PitchRads", pitchRads) + table.put("RollRads", rollRads) + table.put("FlPos", flPos) + table.put("FrPos", frPos) + table.put("BlPos", blPos) + table.put("BrPos", brPos) + } + + override fun fromLog(table: LogTable) { + yawRads = table.get("YawRads", yawRads) + pitchRads = table.get("PitchRads", pitchRads) + rollRads = table.get("RollRads", rollRads) + flPos = table.get("FlPos", flPos) + frPos = table.get("FrPos", frPos) + blPos = table.get("BlPos", blPos) + brPos = table.get("BrPos", brPos) + } +} +``` + + + +:::tip +For Kotlin users, [automatic input logging](#automatic-input-logging) is available to skip writing these repetitive method implementations. +::: + +## Input Processing +In your subsystem periodic, this object can then be updated with new data, and these objects are passed +to the ``Logger`` for processing. + + + + +```kotlin +// Beginning of periodic loop, before your logic. +io.updateInputs(inputs) +Logger.processInputs(inputs) + +// Your periodic logic continues here. +``` + + + +```java +// Beginning of periodic loop, before your logic. +io.updateInputs(inputs); +Logger.processInputs(inputs); + +// Your periodic logic continues here. +``` + + + +:::warning +This should be done every subsystem periodic loop **before** referencing inputs. This ensures the +current cycle is logged and synchronous. +::: + +## Automatic Input Logging +For kotlin users looking to simplify their code and skip writing repetitive logtable operations, +your input schemas can extend from the abstract class ``AutoLoggableInputs``, using delegates +for input fields. + +The below is an example of the same input object from above, but changed to make use +of automatic input logging. + +```kt +class DriveInputs : AutoLoggableInputs() { + var yawRads by logged("YawRads", 0.0) + var pitchRads by logged("PitchRads", 0.0) + var rollRads by logged("RollRads", 0.0) + + var flPos by logged("FlPos", 0.0) + var frPos by logged("FrPos", 0.0) + var blPos by logged("BlPos", 0.0) + var brPos by logged("BrPos", 0.0) +} +``` + +:::warning +This feature is not available for Java users, as the implementation +relies heavily on Kotlin's language features. +::: \ No newline at end of file diff --git a/docs/docs/functionality/outputs.md b/docs/docs/functionality/outputs.md index 2773791..8c57a8a 100644 --- a/docs/docs/functionality/outputs.md +++ b/docs/docs/functionality/outputs.md @@ -1,5 +1,5 @@ --- -title: "Outputs" +title: "Output Logging" --- import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; @@ -15,7 +15,7 @@ Logger.output("YourKey", "YourValue") Logger.output("Lift/Setpoint", setpointPosition) Logger.output("RobotState", State.COLLECTING) // Enum ``` - +in