From 4082def1d67d6c4f5268d4247a07812afc34cb5d Mon Sep 17 00:00:00 2001 From: blaze-developer Date: Thu, 8 Jan 2026 10:39:25 -0800 Subject: [PATCH 1/4] Potential AutoLog inputs --- .../chrono/structure/LoggableInputs.kt | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) 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..cf12207 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,48 @@ package com.blazedeveloper.chrono.structure +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<*>) { + toLogs.add { it.toLog(key, value) } + fromLogs.add { it.fromLog(key, value) } + } + } + + 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) + + 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 From 518e63d463e1bc208438b629d93ab1e282968708 Mon Sep 17 00:00:00 2001 From: blaze-developer Date: Thu, 8 Jan 2026 11:46:16 -0800 Subject: [PATCH 2/4] Fix Method --- .../java/com/blazedeveloper/chrono/structure/LoggableInputs.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 cf12207..49c3ed5 100644 --- a/chrono/src/main/java/com/blazedeveloper/chrono/structure/LoggableInputs.kt +++ b/chrono/src/main/java/com/blazedeveloper/chrono/structure/LoggableInputs.kt @@ -17,9 +17,10 @@ abstract class AutoLoggableInputs : LoggableInputs { 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<*>) { + operator fun provideDelegate(thisRef: Any, property: KProperty<*>): Field { toLogs.add { it.toLog(key, value) } fromLogs.add { it.fromLog(key, value) } + return this } } From 285c46209b04fd958f8cf3e622fd90a6fc746186 Mon Sep 17 00:00:00 2001 From: blaze-developer Date: Fri, 9 Jan 2026 00:41:36 -0800 Subject: [PATCH 3/4] Update Docs and fix color ordering --- .../chrono/structure/LogTable.kt | 14 +- .../chrono/structure/LoggableInputs.kt | 2 + docs/docs/functionality/data-types.md | 10 +- docs/docs/functionality/inputs.md | 125 ++++++++++++++++++ docs/docs/functionality/outputs.md | 4 +- docs/docs/setup/opmode/index.md | 6 +- 6 files changed, 150 insertions(+), 11 deletions(-) create mode 100644 docs/docs/functionality/inputs.md 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 49c3ed5..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,5 +1,6 @@ package com.blazedeveloper.chrono.structure +import com.qualcomm.robotcore.hardware.NormalizedRGBA import kotlin.reflect.KProperty interface LoggableInputs { @@ -40,6 +41,7 @@ abstract class AutoLoggableInputs : LoggableInputs { 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>() 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 diff --git a/docs/docs/setup/opmode/index.md b/docs/docs/setup/opmode/index.md index 7a4d391..9758a54 100644 --- a/docs/docs/setup/opmode/index.md +++ b/docs/docs/setup/opmode/index.md @@ -27,7 +27,11 @@ Chrono handles and logs a few built-in inputs, including [synchronized timestamp gamepads, and events like opmode init, active, and stop requests. ### Timestamps -Although Chrono cannot override built in methods like ``System.nanoTime`` with its own time source, +Although Chrono cannot override built in methods like ``### Lifecycle Events +Opmode lifecycle events need to come from a logged and deterministic source to guarantee replay +accuracy. In your opmodes, ensure that you always use members ``isActive``, ``inInit``, and``shouldStop`` +as opposed to the methods ``opModeIsActive``, ``opModeInInit``, and ``isStopRequested``. +System.nanoTime`` with its own time source, it provides ``Logger.timestamp`` as the way to fetch a deterministic and synchronized timestamp. This should be used for all replayed logic, and methods like ``System.nanoTime`` should only be used in hardware implementations / non-replayed logic. From 0a4ba22eb8cf28479ecbc5a90b70031ec003d55d Mon Sep 17 00:00:00 2001 From: blaze-developer Date: Fri, 9 Jan 2026 00:43:07 -0800 Subject: [PATCH 4/4] Fix doc --- docs/docs/setup/opmode/index.md | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/docs/docs/setup/opmode/index.md b/docs/docs/setup/opmode/index.md index 9758a54..7a4d391 100644 --- a/docs/docs/setup/opmode/index.md +++ b/docs/docs/setup/opmode/index.md @@ -27,11 +27,7 @@ Chrono handles and logs a few built-in inputs, including [synchronized timestamp gamepads, and events like opmode init, active, and stop requests. ### Timestamps -Although Chrono cannot override built in methods like ``### Lifecycle Events -Opmode lifecycle events need to come from a logged and deterministic source to guarantee replay -accuracy. In your opmodes, ensure that you always use members ``isActive``, ``inInit``, and``shouldStop`` -as opposed to the methods ``opModeIsActive``, ``opModeInInit``, and ``isStopRequested``. -System.nanoTime`` with its own time source, +Although Chrono cannot override built in methods like ``System.nanoTime`` with its own time source, it provides ``Logger.timestamp`` as the way to fetch a deterministic and synchronized timestamp. This should be used for all replayed logic, and methods like ``System.nanoTime`` should only be used in hardware implementations / non-replayed logic.