Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -92,9 +92,9 @@ class LogTable @JvmOverloads constructor(
fun <E : Enum<E>> put(key: String, value: Array<E>) =
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].
Expand Down Expand Up @@ -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]
}
}
}
Original file line number Diff line number Diff line change
@@ -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<T>(
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<T> {
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<String>) = Field(key, value, LogTable::put, LogTable::get)
inline fun <reified E: Enum<E>> logged(key: String, value: E) = Field(key, value, LogTable::put, LogTable::get)
inline fun <reified E: Enum<E>> logged(key: String, value: Array<E>) = 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) }
}
10 changes: 9 additions & 1 deletion docs/docs/functionality/data-types.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
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
125 changes: 125 additions & 0 deletions docs/docs/functionality/inputs.md
Original file line number Diff line number Diff line change
@@ -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:

<Tabs groupId="lang" queryString="lang">
<TabItem value="kt" label="Kotlin" default>

```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)
}
}
```
</TabItem>
</Tabs>

:::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.

<Tabs groupId="lang" queryString="lang">
<TabItem value="kt" label="Kotlin" default>

```kotlin
// Beginning of periodic loop, before your logic.
io.updateInputs(inputs)
Logger.processInputs(inputs)

// Your periodic logic continues here.
```
</TabItem>
<TabItem value="java" label="Java" default>

```java
// Beginning of periodic loop, before your logic.
io.updateInputs(inputs);
Logger.processInputs(inputs);

// Your periodic logic continues here.
```
</TabItem>
</Tabs>

:::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.
:::
4 changes: 2 additions & 2 deletions docs/docs/functionality/outputs.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
title: "Outputs"
title: "Output Logging"
---
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
Expand All @@ -15,7 +15,7 @@ Logger.output("YourKey", "YourValue")
Logger.output("Lift/Setpoint", setpointPosition)
Logger.output("RobotState", State.COLLECTING) // Enum
```

in
</TabItem>
<TabItem value="java" label="Java">

Expand Down