From 338dc8a9dfba86c2dbea10e3f3579b1b1e78664d Mon Sep 17 00:00:00 2001 From: Carl Stanton Date: Tue, 21 Apr 2026 23:39:58 -0400 Subject: [PATCH 1/3] Added yagsl and advantagekit plugins. --- .claude/settings.local.json | 7 +- config.json | 8 + .../plugins/advantagekit/__init__.py | 5 + .../plugins/advantagekit/build_index.py | 273 +++++++++++ .../plugins/advantagekit/data/index.json | 335 +++++++++++++ src/wpilib_mcp/plugins/advantagekit/plugin.py | 235 +++++++++ src/wpilib_mcp/plugins/yagsl/__init__.py | 5 + src/wpilib_mcp/plugins/yagsl/build_index.py | 253 ++++++++++ src/wpilib_mcp/plugins/yagsl/data/index.json | 463 ++++++++++++++++++ src/wpilib_mcp/plugins/yagsl/plugin.py | 197 ++++++++ 10 files changed, 1780 insertions(+), 1 deletion(-) create mode 100644 src/wpilib_mcp/plugins/advantagekit/__init__.py create mode 100644 src/wpilib_mcp/plugins/advantagekit/build_index.py create mode 100644 src/wpilib_mcp/plugins/advantagekit/data/index.json create mode 100644 src/wpilib_mcp/plugins/advantagekit/plugin.py create mode 100644 src/wpilib_mcp/plugins/yagsl/__init__.py create mode 100644 src/wpilib_mcp/plugins/yagsl/build_index.py create mode 100644 src/wpilib_mcp/plugins/yagsl/data/index.json create mode 100644 src/wpilib_mcp/plugins/yagsl/plugin.py diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 970a46c..e7e9cd4 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -7,7 +7,12 @@ "WebFetch(domain:docs.photonvision.org)", "WebFetch(domain:docs.reduxrobotics.com)", "Bash(uv run pytest:*)", - "Bash(uv run:*)" + "Bash(uv run:*)", + "Bash(python3 -c ' *)", + "WebFetch(domain:docs.yagsl.com)", + "WebFetch(domain:docs.advantagekit.org)", + "WebFetch(domain:raw.githubusercontent.com)", + "WebFetch(domain:yagsl.gitbook.io)" ] } } diff --git a/config.json b/config.json index 95cfd09..ee5f77a 100644 --- a/config.json +++ b/config.json @@ -20,6 +20,14 @@ "photonvision": { "enabled": true, "languages": ["Java", "C++", "Python"] + }, + "yagsl": { + "enabled": true, + "languages": ["Java"] + }, + "advantagekit": { + "enabled": true, + "languages": ["Java"] } }, "cache": { diff --git a/src/wpilib_mcp/plugins/advantagekit/__init__.py b/src/wpilib_mcp/plugins/advantagekit/__init__.py new file mode 100644 index 0000000..f1f255f --- /dev/null +++ b/src/wpilib_mcp/plugins/advantagekit/__init__.py @@ -0,0 +1,5 @@ +"""AdvantageKit documentation plugin.""" + +from .plugin import Plugin + +__all__ = ["Plugin"] diff --git a/src/wpilib_mcp/plugins/advantagekit/build_index.py b/src/wpilib_mcp/plugins/advantagekit/build_index.py new file mode 100644 index 0000000..b1c0fbf --- /dev/null +++ b/src/wpilib_mcp/plugins/advantagekit/build_index.py @@ -0,0 +1,273 @@ +#!/usr/bin/env python3 +""" +Build documentation index for AdvantageKit. + +Uses the known sitemap URL list. Attempts to fetch raw Markdown from the +Mechanical-Advantage/AdvantageKit GitHub repo first; falls back to HTML. + +Usage: + python -m wpilib_mcp.plugins.advantagekit.build_index + + # Or from the plugin directory: + python build_index.py +""" + +import argparse +import asyncio +import json +import logging +import re +import sys +from dataclasses import asdict +from datetime import datetime +from pathlib import Path +from typing import Optional + +import httpx +from bs4 import BeautifulSoup + +try: + from wpilib_mcp.utils.indexer import PageData + from wpilib_mcp.utils.markdown import extract_md_title, detect_md_language +except ImportError: + sys.path.insert(0, str(Path(__file__).parent.parent.parent.parent)) + from wpilib_mcp.utils.indexer import PageData + from wpilib_mcp.utils.markdown import extract_md_title, detect_md_language + + +logging.basicConfig( + level=logging.INFO, + format="%(asctime)s - %(levelname)s - %(message)s" +) +logger = logging.getLogger(__name__) + +BASE_URL = "https://docs.advantagekit.org" +GITHUB_RAW = "https://raw.githubusercontent.com/Mechanical-Advantage/AdvantageKit/main/docs/docs" +RATE_LIMIT = 0.3 + +# Content pages from sitemap โ€” category index pages and /search excluded +SITEMAP_URLS = [ + "https://docs.advantagekit.org/getting-started/what-is-advantagekit/", + "https://docs.advantagekit.org/getting-started/what-is-advantagekit/champs-conference", + "https://docs.advantagekit.org/getting-started/what-is-advantagekit/example-bug-fixes", + "https://docs.advantagekit.org/getting-started/what-is-advantagekit/example-output-logging", + "https://docs.advantagekit.org/getting-started/what-is-advantagekit/example-rapid-iteration", + "https://docs.advantagekit.org/getting-started/installation/", + "https://docs.advantagekit.org/getting-started/installation/existing-projects", + "https://docs.advantagekit.org/getting-started/installation/version-control", + "https://docs.advantagekit.org/getting-started/installation/vscode-welcome", + "https://docs.advantagekit.org/getting-started/template-projects", + "https://docs.advantagekit.org/getting-started/template-projects/diff-drive-template", + "https://docs.advantagekit.org/getting-started/template-projects/kitbot-template", + "https://docs.advantagekit.org/getting-started/template-projects/skeleton-template", + "https://docs.advantagekit.org/getting-started/template-projects/spark-swerve-template", + "https://docs.advantagekit.org/getting-started/template-projects/talonfx-swerve-template", + "https://docs.advantagekit.org/getting-started/template-projects/vision-template", + "https://docs.advantagekit.org/getting-started/traditional-replay", + "https://docs.advantagekit.org/getting-started/replay-watch", + "https://docs.advantagekit.org/getting-started/common-issues", + "https://docs.advantagekit.org/getting-started/common-issues/multithreading", + "https://docs.advantagekit.org/getting-started/common-issues/non-deterministic-data-sources", + "https://docs.advantagekit.org/getting-started/common-issues/uninitialized-inputs", + "https://docs.advantagekit.org/data-flow/supported-types", + "https://docs.advantagekit.org/data-flow/built-in-logging", + "https://docs.advantagekit.org/data-flow/recording-inputs", + "https://docs.advantagekit.org/data-flow/recording-inputs/io-interfaces", + "https://docs.advantagekit.org/data-flow/recording-inputs/annotation-logging", + "https://docs.advantagekit.org/data-flow/recording-inputs/dashboard-inputs", + "https://docs.advantagekit.org/data-flow/recording-outputs/", + "https://docs.advantagekit.org/data-flow/recording-outputs/annotation-logging", + "https://docs.advantagekit.org/data-flow/sysid-compatibility", + "https://docs.advantagekit.org/theory/log-replay-comparison", + "https://docs.advantagekit.org/theory/deterministic-timestamps", + "https://docs.advantagekit.org/theory/high-frequency-odometry", + "https://docs.advantagekit.org/theory/case-studies", + "https://docs.advantagekit.org/theory/case-studies/aiming-functions", + "https://docs.advantagekit.org/theory/case-studies/apriltag-tuning", + "https://docs.advantagekit.org/theory/case-studies/autoscoring", + "https://docs.advantagekit.org/theory/case-studies/command-gremlins", + "https://docs.advantagekit.org/theory/case-studies/elevator-profile", + "https://docs.advantagekit.org/theory/case-studies/retroreflective-tuning", + "https://docs.advantagekit.org/whats-new", +] + + +def extract_section(url: str) -> str: + u = url.lower() + if "/getting-started/installation" in u: + return "Installation" + elif "/getting-started/template-projects" in u: + return "Template Projects" + elif "/getting-started/common-issues" in u: + return "Common Issues" + elif "/getting-started/" in u: + return "Getting Started" + elif "/data-flow/recording-inputs" in u: + return "Recording Inputs" + elif "/data-flow/recording-outputs" in u: + return "Recording Outputs" + elif "/data-flow/" in u: + return "Data Flow" + elif "/theory/case-studies" in u: + return "Case Studies" + elif "/theory/" in u: + return "Theory" + elif "/whats-new" in u: + return "What's New" + return "General" + + +def to_github_raw_url(doc_url: str) -> Optional[str]: + """Convert docs.advantagekit.org URL to a GitHub raw markdown candidate URL.""" + prefix = "https://docs.advantagekit.org/" + if not doc_url.startswith(prefix): + return None + path = doc_url[len(prefix):].rstrip("/") + if not path: + return None + # Trailing-slash URLs are directory index pages + if doc_url.endswith("/"): + return f"{GITHUB_RAW}/{path}/index.md" + return f"{GITHUB_RAW}/{path}.md" + + +def extract_docusaurus_content(soup: BeautifulSoup) -> str: + """Extract main content from a Docusaurus HTML page.""" + for selector in [ + "nav", "header", "footer", ".navbar", ".sidebar", + ".theme-doc-toc-desktop", ".pagination-nav", + ".theme-doc-breadcrumbs", "script", "style", + ]: + for elem in soup.select(selector): + elem.decompose() + + main = ( + soup.find("article") or + soup.find("main") or + soup.body + ) + if main is None: + return "" + + text = main.get_text(separator=" ", strip=True) + return re.sub(r"\s+", " ", text).strip() + + +def create_preview(content: str, max_length: int = 300) -> str: + if len(content) <= max_length: + return content + preview = content[:max_length] + last_period = preview.rfind(". ") + if last_period > max_length * 0.5: + return preview[:last_period + 1] + last_space = preview.rfind(" ") + if last_space > max_length * 0.7: + return preview[:last_space] + "..." + return preview + "..." + + +async def build(version: str = "latest") -> dict: + pages: list[PageData] = [] + + async with httpx.AsyncClient( + timeout=30.0, + follow_redirects=True, + headers={"User-Agent": "FIRST-Agentic-CSA-advantagekit-Indexer/1.0"} + ) as client: + for url in SITEMAP_URLS: + await asyncio.sleep(RATE_LIMIT) + + content = None + title = None + language = "Java" + + # Try GitHub raw markdown first + gh_url = to_github_raw_url(url) + if gh_url: + try: + resp = await client.get(gh_url) + resp.raise_for_status() + markdown = resp.text + if len(markdown.strip()) >= 100: + title = extract_md_title(markdown) + language = detect_md_language(markdown) or "Java" + content = markdown + logger.debug(f"Got markdown from GitHub: {gh_url}") + except Exception: + pass + + # Fall back to HTML + if not content: + try: + resp = await client.get(url) + resp.raise_for_status() + soup = BeautifulSoup(resp.text, "lxml") + content = extract_docusaurus_content(soup) + if not title: + h1 = soup.find("h1") + if h1: + title = h1.get_text(strip=True) + elif soup.title: + t = soup.title.string or "" + for sep in [" | ", " - ", " โ€” ", " ยท "]: + if sep in t: + title = t.split(sep)[0].strip() + break + else: + title = t.strip() + except Exception as e: + logger.warning(f"Failed to fetch {url}: {e}") + continue + + if not content or len(content) < 50: + logger.warning(f"Skipping short/empty page: {url}") + continue + + if not title: + title = url.rstrip("/").split("/")[-1].replace("-", " ").title() + + section = extract_section(url) + pages.append(PageData( + url=url, + title=title, + section=section, + language=language, + content=content, + content_preview=create_preview(content), + )) + logger.info(f"Indexed: {title}") + + logger.info(f"Indexed {len(pages)} pages for advantagekit") + return { + "vendor": "advantagekit", + "version": version, + "built_at": datetime.now().isoformat(), + "pages": [asdict(p) for p in pages], + } + + +async def main(): + parser = argparse.ArgumentParser(description="Build AdvantageKit documentation index") + parser.add_argument("--output", type=Path, + help="Output file path (default: data/index.json)") + parser.add_argument("--verbose", "-v", action="store_true") + args = parser.parse_args() + + if args.verbose: + logging.getLogger().setLevel(logging.DEBUG) + + data_dir = Path(__file__).parent / "data" + output_path = args.output or (data_dir / "index.json") + + index = await build("latest") + + output_path.parent.mkdir(parents=True, exist_ok=True) + with open(output_path, "w", encoding="utf-8") as f: + json.dump(index, f, indent=2, ensure_ascii=False) + + print(f"\nโœ“ AdvantageKit index saved to {output_path}") + print(f" Pages indexed: {len(index['pages'])}") + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/src/wpilib_mcp/plugins/advantagekit/data/index.json b/src/wpilib_mcp/plugins/advantagekit/data/index.json new file mode 100644 index 0000000..9bc1ead --- /dev/null +++ b/src/wpilib_mcp/plugins/advantagekit/data/index.json @@ -0,0 +1,335 @@ +{ + "vendor": "advantagekit", + "version": "latest", + "built_at": "2026-04-21T23:37:46.571546", + "pages": [ + { + "url": "https://docs.advantagekit.org/getting-started/what-is-advantagekit/", + "title": "๐Ÿ‘‹ What is AdvantageKit?", + "section": "Getting Started", + "language": "All", + "content": "# ๐Ÿ‘‹ What is AdvantageKit?\n\nAdvantageKit is a logging framework. But to understand it, we need to start by looking at FRC robot code in general. Here's what all robot code fundamentally looks like:\n\n![Logging Diagram #1](img/what-is-diagram-1.png)\n\nThere are **inputs** like sensors and driver inputs and **outputs** like motor commands and other calculated values. The task of the **robot code** is to convert **inputs** into **outputs**.\n\nA variety of logging frameworks already exist in FRC, from built-in tools like the Driver Station and [WPILib logging](https://docs.wpilib.org/en/stable/docs/software/telemetry/datalog.html) all the way to fully custom solutions. Most of these frameworks follow a similar structure, which looks this:\n\n![Logging Diagram #2](img/what-is-diagram-2.png)\n\nA limited set of values are provided by the robot code and stored in the log. That could include sensor data, PID error, odometry pose, output commands, etc. While this has enormous value, it doesn't solve the eternal sentiment when something goes wrong: \"If only we were logging one extra field!\" Notice above that there is data flowing in and out of the robot code without being saved to the log file.\n\nAdvantageKit takes a different approach. This is what logging looks like with AdvantageKit:\n\n![Logging Diagram #3](img/what-is-diagram-3.png)\n\nInstead of logging a limited set of values from the user code, AdvantageKit records _all of the data flowing into the robot code_. Every sensor value, button press, and much more is logged every loop cycle. After a match, these values can be replayed to the robot code in a simulator. Since every input command is the same, all of the internal logic of the code is replayed exactly. This allows you to log extra fields after the fact, or modify pipelines to see how they _would have_ functioned during the match. This technique means that logging is more than just a tool for checking on specific issues; it's also a safety net that can be used to verify how any part of the code functions.\n\n:::tip\nTo see what log replay looks like in practice, check the sections below on \"The Basics\" or find real case studies under the \"Theory\" section.\n:::\n", + "content_preview": "# ๐Ÿ‘‹ What is AdvantageKit?\n\nAdvantageKit is a logging framework. But to understand it, we need to start by looking at FRC robot code in general. Here's what all robot code fundamentally looks like:\n\n![Logging Diagram #1](img/what-is-diagram-1.png)\n\nThere are **inputs** like sensors and driver inputs..." + }, + { + "url": "https://docs.advantagekit.org/getting-started/what-is-advantagekit/champs-conference", + "title": "Championship Conference", + "section": "Getting Started", + "language": "All", + "content": "---\nsidebar_position: 1\n---\n\n# Championship Conference\n\nThe following conference was presented by Team 6328 at the 2025 FIRST Championship, and provides an accessible overview of the capabilities and use cases of AdvantageKit.\n\n\n", + "content_preview": "---\nsidebar_position: 1\n---\n\n# Championship Conference\n\nThe following conference was presented by Team 6328 at the 2025 FIRST Championship, and provides an accessible overview of the capabilities and use cases of AdvantageKit.\n\n\n\n:::note\nThe replay process shown in the video is in running in real-time. The replay duration depends on the size of the log file, complexity of robot project, and CPU performance of the development computer.\n:::\n\nSeveral versions of the vision gains are tested in the video:\n\n- **Normal trust in vision** produces identical outputs to the original pose estimates, which validates that deterministic replay is working correctly.\n\n![Normal tuning](./img/example-iteration-1.png)\n\n- **High trust in vision** produces more responsive but much noisier pose estimates.\n\n![High trust in vision](./img/example-iteration-2.png)\n\n- **Low trust in vision** produces stabler but less responsive pose estimates, incorrectly showing the robot pass beyond the purple game piece in the middle of the field.\n\n![Low trust in vision](./img/example-iteration-3.png)\n", + "content_preview": "---\nsidebar_position: 4\n---\n\n# The Basics: Rapid Iteration\n\nSome uses cases of log replay benefit from rapid iteration. One such example is tuning pose estimation algorithms." + }, + { + "url": "https://docs.advantagekit.org/getting-started/installation/", + "title": "๐Ÿ“ฆ Installation", + "section": "Installation", + "language": "All", + "content": "# ๐Ÿ“ฆ Installation\n\n:::tip\nLooking to install AdvantageKit in a [Python robot project](https://docs.wpilib.org/en/stable/docs/software/python/index.html)? Consider using **[PyKit](https://github.com/1757WestwoodRobotics/PyKit)**, an alternative to AdvantageKit developed by [Team 1757](https://whsrobotics.org) that supports deterministic replay in Python.\n:::\n\n## New Projects\n\n:::info\nTemplate projects are not currently available for the 2027 alpha versions of AdvantageKit.\n:::\n\nFor new projects, we highly recommend starting with one of the [template projects](/getting-started/template-projects) attached to the [latest release](https://github.com/Mechanical-Advantage/AdvantageKit/releases). These projects include detailed documentation and setup instructions for many common use cases:\n\n- **[2026 KitBot Template](../template-projects/kitbot-template.md)**: For robots based on the 2026 FIRST KitBot.\n- **[Differential Drive Template](../template-projects/diff-drive-template.md)**: For other differential drive (tank) robots.\n- **[Spark Swerve Template](../template-projects/spark-swerve-template.md)**: For swerve drives primarily using the Spark Max and Spark Flex, including NEO, NEO Vortex, or NEO 550 motors.\n- **[TalonFX(S) Swerve Template](../template-projects/talonfx-swerve-template.md)**: For swerve drives primarily using TalonFX(S)-based motors like the Falcon 500, Kraken X60, Kraken X44, and Minion.\n- **[Vision Template](../template-projects/vision-template.md)**: Example code for running simple vision targeting and pose estimation.\n- **[Skeleton Template](../template-projects/skeleton-template.md)**: Simple project with AdvantageKit installed but without subsystems or control logic.\n\n## Existing Projects\n\nUsers wishing to install AdvantageKit in an existing project should check the documentation page for [existing projects](./existing-projects.md).\n\n## Offline Installation\n\nMaven artifacts for AdvantageKit can be downloaded and installed for offline use. This allows AdvantageKit to be accessed even if the Maven repository is blocked on school networks.\n\n1. Download the \"maven_offline.zip\" asset attached to the latest [GitHub release](https://github.com/Mechanical-Advantage/AdvantageKit/releases/latest).\n2. Unzip the file into \"C:\\Users\\Public\\wpilib\\YEAR\\maven\" on Windows or \"~/wpilib/YEAR/maven\" on macOS/Linux.\n\n## Legacy Projects\n\nProjects based on AdvantageKit v4.0.0-beta-1 or earlier may experience build failures due to the use of an invalid GitHub Packages token. To address this issue, these releases have been republished to the current Maven repository used by v4.0.0 and later (which does not require authentication). Please follow the steps below to switch to the new Maven repository:\n\n1. Ensure that you are using the _original_ vendordep JSON file (all versions of the vendordep JSON can be found on the [GitHub releases page](https://github.com/Mechanical-Advantage/AdvantageKit/releases)). **Do not modify this JSON file.**\n\n2. Find the block below in `build.gradle` which configured the GitHub Packages repository:\n\n```groovy\nrepositories {\n maven {\n url = uri(\"https://maven.pkg.github.com/Mechanical-Advantage/AdvantageKit\")\n credentials {\n username = \"Mechanical-Advantage-Bot\"\n password = \"\\u0067\\u0068\\u0070\\u005f\\u006e\\u0056\\u0051\\u006a\\u0055\\u004f\\u004c\\u0061\\u0079\\u0066\\u006e\\u0078\\u006e\\u0037\\u0051\\u0049\\u0054\\u0042\\u0032\\u004c\\u004a\\u006d\\u0055\\u0070\\u0073\\u0031\\u006d\\u0037\\u004c\\u005a\\u0030\\u0076\\u0062\\u0070\\u0063\\u0051\"\n }\n }\n}\n```\n\n3. Replace that block with the new version shown below:\n\n```groovy\nrepositories {\n maven {\n url = uri(\"https://frcmaven.wpi.edu/artifactory/littletonrobotics-mvn-release\")\n }\n}\n```\n", + "content_preview": "# ๐Ÿ“ฆ Installation\n\n:::tip\nLooking to install AdvantageKit in a [Python robot project](https://docs.wpilib.org/en/stable/docs/software/python/index.html)? Consider using **[PyKit](https://github.com/1757WestwoodRobotics/PyKit)**, an alternative to AdvantageKit developed by [Team..." + }, + { + "url": "https://docs.advantagekit.org/getting-started/installation/existing-projects", + "title": "Existing Projects", + "section": "Installation", + "language": "Java", + "content": "---\nsidebar_position: 1\n---\n\n# Existing Projects\n\nTo install the AdvantageKit vendordep, follow the instructions in the WPILib documentation for [installing vendor libraries](https://docs.wpilib.org/en/stable/docs/software/vscode-overview/3rd-party-libraries.html#installing-libraries) and choose \"AdvantageKit\" from the list. Alternatively, go to \"WPILib: Manage Vendor Libraries\" > \"Install new libraries (online)\" in VSCode and paste the URL below.\n\n```\nhttps://github.com/Mechanical-Advantage/AdvantageKit/releases/latest/download/AdvantageKit.json\n```\n\nNext, add the following blocks to the `build.gradle` file. Note that the lines under `dependencies` should be combined with the existing `dependencies` block.\n\n```groovy\ntask(replayWatch, type: JavaExec) {\n mainClass = \"org.littletonrobotics.junction.ReplayWatch\"\n classpath = sourceSets.main.runtimeClasspath\n}\n\ndependencies {\n // ...\n def akitJson = new groovy.json.JsonSlurper().parseText(new File(projectDir.getAbsolutePath() + \"/vendordeps/AdvantageKit.json\").text)\n annotationProcessor \"org.littletonrobotics.akit:akit-autolog:$akitJson.version\"\n}\n```\n\n## Robot Configuration\n\nThe main `Robot` class **must inherit from `LoggedRobot`** (see below). `LoggedRobot` performs the same functions as `TimedRobot`, with some exceptions:\n\n- It does not support adding extra periodic functions.\n- The method `setUseTiming` allows the user code to disable periodic timing and run cycles as fast as possible during replay. The timestamp read by methods like `Timer.getFPGATimstamp()` will still match the timestamp from the real robot.\n\n```java\npublic class Robot extends LoggedRobot {\n ...\n}\n```\n\nThe user program is responsible for configuring and initializing the logging framework. This setup should be placed in the constructor of `Robot` _before any other initialization_. More information on the importance of initializing the robot code in order can be found [here](/getting-started/common-issues/uninitialized-inputs). An example configuration is provided below:\n\n```java\nLogger.recordMetadata(\"ProjectName\", \"MyProject\"); // Set a metadata value\n\nif (isReal()) {\n Logger.addDataReceiver(new WPILOGWriter()); // Log to a USB stick (\"/U/logs\")\n Logger.addDataReceiver(new NT4Publisher()); // Publish data to NetworkTables\n} else {\n setUseTiming(false); // Run as fast as possible\n String logPath = LogFileUtil.findReplayLog(); // Pull the replay log from AdvantageScope (or prompt the user)\n Logger.setReplaySource(new WPILOGReader(logPath)); // Read replay log\n Logger.addDataReceiver(new WPILOGWriter(LogFileUtil.addPathSuffix(logPath, \"_sim\"))); // Save outputs to a new log\n}\n\nLogger.start(); // Start logging! No more data receivers, replay sources, or metadata values may be added.\n```\n\n:::info\nBy default, the `WPILOGWriter` class writes to a USB stick when running on the roboRIO. **A FAT32 formatted USB stick must be connected to one of the roboRIO USB ports**.\n:::\n\nThis setup enters replay mode for all simulator runs. If you need to run the simulator without replay (e.g. a physics simulator or Romi), extra constants or selection logic is required. See the template projects for one method of implementing this logic.\n", + "content_preview": "---\nsidebar_position: 1\n---\n\n# Existing Projects\n\nTo install the AdvantageKit vendordep, follow the instructions in the WPILib documentation for [installing vendor libraries](https://docs.wpilib.org/en/stable/docs/software/vscode-overview/3rd-party-libraries.html#installing-libraries) and choose..." + }, + { + "url": "https://docs.advantagekit.org/getting-started/installation/version-control", + "title": "Version Control", + "section": "Installation", + "language": "Java", + "content": "---\nsidebar_position: 2\n---\n\n# Version Control\n\nTypically, log replay requires that the code running on the robot and in the simulator are identical. We recommend the following tools to achieve that, enabled by version control with Git.\n\n:::tip\nThese tools are already installed in the AdvantageKit [template projects](/getting-started/template-projects), but this page explains their intended usage in more detail.\n:::\n\n## Gversion\n\nThe [gversion](https://github.com/lessthanoptimal/gversion-plugin) Gradle plugin produces a constants file with important metadata, including the [Git hash](https://www.mikestreety.co.uk/blog/the-git-commit-hash/) uniquely identifying each commit. It also includes whether the tree is \"dirty\" (if it included uncommitted changes). The template projects include Gversion already. Otherwise, follow the installation instructions below.\n\n
\nInstallation\n\nAdd the plugin at the top of `build.gradle`:\n\n```groovy\nplugins {\n // ...\n id \"com.peterabeles.gversion\" version \"1.10\"\n}\n```\n\nAdd the `createVersionFile` task as a dependency of `compileJava`:\n\n```groovy\nproject.compileJava.dependsOn(createVersionFile)\ngversion {\n srcDir = \"src/main/java/\"\n classPackage = \"frc.robot\"\n className = \"BuildConstants\"\n dateFormat = \"yyyy-MM-dd HH:mm:ss z\"\n timeZone = \"America/New_York\" // Use preferred time zone\n indent = \" \"\n}\n```\n\nYou should also add the `BuildConstants.java` file to the repository `.gitignore`:\n\n```\nsrc/main/java/frc/robot/BuildConstants.java\n```\n\n:::info\nGit must be installed and available on the PATH to use the Gversion plugin. See [here](https://git-scm.com/downloads).\n:::\n\n
\n\nMetadata can be recorded in the log file as shown below:\n\n```java\npublic Robot() {\n Logger.recordMetadata(\"GitSHA\", BuildConstants.GIT_SHA);\n // ...\n\n Logger.start();\n}\n```\n\nThe metadata values can be viewed using AdvantageScope's ๐Ÿ” [Metadata](https://docs.advantagescope.org/tab-reference/metadata) tab. Running `git checkout ??????...` with the commit hash will return to the same version of code that was running on the robot (except for any uncommitted changes).\n\n## Event Deploy\n\nCode often changes repeatedly during competition, which would normally mean running code with uncommitted changes. This is a problem for log replay, since the version of code running in a particular match may be impossible to recreate afterwards. This can be addressed by including a Gradle task to automatically commit working changes to a temporary branch before every deploy.\n\n### Installation\n\nThe Gradle task is preconfigured in the AdvantageKit template projects. Add the following lines to `build.gradle`:\n\n```groovy\n// Create commit with working changes on event branches\ntask(eventDeploy) {\n doLast {\n if (project.gradle.startParameter.taskNames.any({ it.toLowerCase().contains(\"deploy\") })) {\n def branchPrefix = \"event\"\n def branch = 'git branch --show-current'.execute().text.trim()\n def commitMessage = \"Update at '${new Date().toString()}'\"\n\n if (branch.startsWith(branchPrefix)) {\n exec {\n workingDir(projectDir)\n executable 'git'\n args 'add', '-A'\n }\n exec {\n workingDir(projectDir)\n executable 'git'\n args 'commit', '-m', commitMessage\n ignoreExitValue = true\n }\n\n println \"Committed to branch: '$branch'\"\n println \"Commit message: '$commitMessage'\"\n } else {\n println \"Not on an event branch, skipping commit\"\n }\n } else {\n println \"Not running deploy task, skipping commit\"\n }\n }\n}\ncreateVersionFile.dependsOn(eventDeploy)\n```\n\n### Usage\n\n1. Before the event, create and check out a branch that starts with \"event\" such as \"event_nhgrs\". We recommend creating a new branch for each event.\n2. Deploy robot code through any method supported by WPILib.\n3. A commit is automatically created with all changes since the last commit before the deploy begins. The name of the commit includes the current timestamp (e.g. \"Update at \"1/31/2022, 8:30:00 AM\").\n4. At the end of the event, the branch can be [\"squashed and merged\"](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/incorporating-changes-from-a-pull-request/about-pull-request-merges#squash-and-merge-your-commits) back to a normal development branch, keeping the Git history clean.\n5. When running log replay, find the commit hash in the log file metadata and run `git checkout` as described in the previous section. This will return to the exact version of code running on the robot (even if the commits were later squashed and merged). Since all of the changes were committed before each deploy, the simulated code is guaranteed to be identical to the original robot code.\n", + "content_preview": "---\nsidebar_position: 2\n---\n\n# Version Control\n\nTypically, log replay requires that the code running on the robot and in the simulator are identical. We recommend the following tools to achieve that, enabled by version control with Git.\n\n:::tip\nThese tools are already installed in the AdvantageKit..." + }, + { + "url": "https://docs.advantagekit.org/getting-started/installation/vscode-welcome", + "title": "Welcome to AdvantageKit", + "section": "Installation", + "language": "Java", + "content": "---\nsidebar_class_name: hidden\n---\n\n# Welcome to AdvantageKit\n\nAdvantageKit is a logging, telemetry, and replay framework developed by Team 6328. AdvantageKit enables log replay, where the full state of the robot code can be replayed in simulation based on a log file ([What is AdvantageKit?](/getting-started/what-is-advantagekit/)).\n\n:::danger\n**_AdvantageKit is not a general-purpose logging framework._** Before continuing, check the documentation for:\n\n- [AdvantageScope](https://docs.advantagescope.org), our robot telemetry application which _does not require AdvantageKit to use_.\n- [WPILib Data Logging](https://docs.wpilib.org/en/stable/docs/software/telemetry/datalog.html), a simpler logging system included in WPILib (does not support log replay in simulation, but covers the needs of most teams).\n\n:::\n\n## REQUIRED\n\n**The steps below are required to complete a valid installation of AdvantageKit.**\n\n:::tip\nInstead of installing AdvantageKit in an existing project, consider using one of AdvantageKit's [template projects](/getting-started/template-projects) to get started more quickly. These projects can be downloaded from the [latest release](https://github.com/Mechanical-Advantage/AdvantageKit/releases/latest) and include prebuilt subsystems for **swerve drives**, **vision systems**, the **FIRST KitBot**, and more!\n:::\n\n1. Add the following blocks to the `build.gradle` file. Note that the lines under `dependencies` should be combined with the existing `dependencies` block.\n\n```groovy\ntask(replayWatch, type: JavaExec) {\n mainClass = \"org.littletonrobotics.junction.ReplayWatch\"\n classpath = sourceSets.main.runtimeClasspath\n}\n\ndependencies {\n // ...\n def akitJson = new groovy.json.JsonSlurper().parseText(new File(projectDir.getAbsolutePath() + \"/vendordeps/AdvantageKit.json\").text)\n annotationProcessor \"org.littletonrobotics.akit:akit-autolog:$akitJson.version\"\n}\n```\n\n2. Configure the main `Robot` class to inherit from `LoggedRobot` as shown below. See [here](./existing-projects.md#robot-configuration) for details.\n\n```java\npublic class Robot extends LoggedRobot {\n ...\n}\n```\n\n3. Initialize the logging framework in the constructor of `Robot` _before any other initialization_. An example configuration is provided below:\n\n```java\nLogger.recordMetadata(\"ProjectName\", \"MyProject\"); // Set a metadata value\n\nif (isReal()) {\n Logger.addDataReceiver(new WPILOGWriter()); // Log to a USB stick (\"/U/logs\")\n Logger.addDataReceiver(new NT4Publisher()); // Publish data to NetworkTables\n} else {\n setUseTiming(false); // Run as fast as possible\n String logPath = LogFileUtil.findReplayLog(); // Pull the replay log from AdvantageScope (or prompt the user)\n Logger.setReplaySource(new WPILOGReader(logPath)); // Read replay log\n Logger.addDataReceiver(new WPILOGWriter(LogFileUtil.addPathSuffix(logPath, \"_sim\"))); // Save outputs to a new log\n}\n\nLogger.start(); // Start logging! No more data receivers, replay sources, or metadata values may be added.\n```\n\n:::info\nBy default, the `WPILOGWriter` class writes to a USB stick when running on the roboRIO. **A FAT32 formatted USB stick must be connected to one of the roboRIO USB ports**.\n:::\n\nThis setup enters replay mode for all simulator runs. If you need to run the simulator without replay (e.g. a physics simulator or Romi), extra constants or selection logic is required. See the template projects for one method of implementing this logic.\n\n:::note\nFor all support requests, please email software@team6328.org.\n:::\n", + "content_preview": "---\nsidebar_class_name: hidden\n---\n\n# Welcome to AdvantageKit\n\nAdvantageKit is a logging, telemetry, and replay framework developed by Team 6328. AdvantageKit enables log replay, where the full state of the robot code can be replayed in simulation based on a log file ([What is..." + }, + { + "url": "https://docs.advantagekit.org/getting-started/template-projects", + "title": "๐Ÿ  Template Projects", + "section": "Template Projects", + "language": "Java", + "content": "2026 KitBot Template Differential Drive Template Spark Swerve Template TalonFX(S) Swerve Template Vision Template Skeleton Template", + "content_preview": "2026 KitBot Template Differential Drive Template Spark Swerve Template TalonFX(S) Swerve Template Vision Template Skeleton Template" + }, + { + "url": "https://docs.advantagekit.org/getting-started/template-projects/diff-drive-template", + "title": "Differential Drive Template", + "section": "Template Projects", + "language": "All", + "content": "---\nsidebar_position: 2\n---\n\n# Differential Drive Template\n\nThe differential drive template is designed for drives based on Spark Max, Talon FX, or Talon SRX controllers and a NavX, Pigeon 2, or similar gyro. Some of the key features of the template include:\n\n- On-controller feedback loops\n- Physics simulation\n- Automated characterization routines\n- Pose estimator integration (not including vision)\n- **Deterministic replay** with a **guarantee of accuracy**\n\n:::info\nThe AdvantageKit differential drive template is **open-source** and **fully customizable**:\n\n- **No black boxes:** Users can view and adjust all layers of the drive control stack.\n- **Customizable:** IO implementations can be adjusted to support any hardware configuration (see the [customization](#customization) section).\n- **Replayable:** Every aspect of the drive control logic, pose estimator, etc. can be replayed and logged in simulation using AdvantageKit's deterministic replay features with _guaranteed accuracy_.\n\n:::\n\n## Setup\n\n1. Download the differential drive template project from the AdvantageKit release on GitHub and open it in VSCode.\n\n2. Click the WPILib icon in the VSCode toolbar and find the task `WPILib: Set Team Number`. Enter your team number and press enter.\n\n3. If not already available, download and install [Git](https://git-scm.com/downloads).\n\n4. If the project will run **only on the roboRIO 2**, uncomment lines 39-42 of `build.gradle`. These contain additional [garbage collection](https://www.geeksforgeeks.org/garbage-collection-java/) optimizations for the RIO 2 to improve performance.\n\n5. Navigate to `src/main/java/frc/robot/subsystems/drive/DriveConstants.java` in the AdvantageKit project.\n\n6. Update the value of `motorReduction` based on the robot's gearing. These values represent reductions and should generally be greater than one.\n\n7. Update the value of `trackWidth` based on the distance between the left and right sets of wheels.\n\n8. Update the value of `wheelRadiusMeters` to the theoretical radius of each wheel. This value can be further refined as described in the \"Tuning\" section below.\n\n9. Update the value of `maxSpeedMetersPerSec` to the theoretical max speed of the robot. This value can be further refined as described in the \"Tuning\" section below.\n\n10. Set the value of `pigeonCanId` to the correct CAN ID of the Pigeon 2 (as configured using Tuner X). **If using a NavX instead of a Pigeon 2, see the [customization](#customization) section below.**\n\n11. Set values of the left and right leader and follower motors to the correct CAN IDs of the drive controllers (as configured in Phoenix Tuner or REV Hardware Client).\n\n12. In the constructor of `RobotContainer`, switch the IO implementations instantiated for the drive based on your chosen hardware. The default is the Talon SRX and Pigeon 2.\n\n13. Deploy the project to the robot and connect using AdvantageScope.\n\n14. Check that there are no dashboard alerts or errors in the Driver Station console. If any errors appear, verify that CAN IDs, firmware versions, and configurations of all devices.\n\n:::warning\nThe project is configured to save log files when running on a real robot. **A FAT32 formatted USB stick must be connected to one of the roboRIO USB ports to save log files.**\n:::\n\n14. Manually rotate each side of the drive and view the position in AdvantageScope (`/Drive/Module.../DrivePositionRad`). Verify that the units visible in AdvantageScope (radians) match the physical motion of the module, and that positive motion corresponds to forward movement of the robot. If necessary, change the value of `motorReduction`, `leftInverted`, or `righInverted`.\n\n## Tuning\n\n### Feedforward Characterization\n\nThe project includes default [feedforward gains](https://docs.wpilib.org/en/stable/docs/software/advanced-controls/introduction/introduction-to-feedforward.html#introduction-to-dc-motor-feedforward) for velocity control (`kS` and `kV`).\n\nThe project includes a simple feedforward routine that can be used to quickly measure the drive `kS` and `kV` values without requiring [SysId](https://docs.wpilib.org/en/stable/docs/software/advanced-controls/system-identification/index.html):\n\n1. Place the robot in an open space.\n\n2. Select the \"Drive Simple FF Characterization\" auto routine.\n\n3. Enable the robot in autonomous. The robot will slowly accelerate forwards, similar to a SysId quasistic test.\n\n4. Disable the robot after at least ~5-10 seconds.\n\n5. Check the console output for the measured `kS` and `kV` values, and copy them to the `realKs` and `realKv` constants in `DriveConstants.java`.\n\n:::info\nThe feedforward model used in simulation can be characterized using the same method. **Simulation gains are stored in the `simKs` and `simKv` constants.**\n:::\n\nUsers who wish to characterize acceleration gains (`kA`) can choose to use the full [SysId](https://docs.wpilib.org/en/stable/docs/software/advanced-controls/system-identification/index.html) application. The project includes auto routines for each of the four required SysId tests. Two options are available to load data in SysId:\n\n- For Spark users, the project is configured to use [URCL](https://docs.advantagescope.org/more-features/urcl) by default. This data can be exported as described [here](https://docs.advantagescope.org/more-features/urcl#sysid-usage).\n- TalonFX (**not Talon SRX**) users can export the Hoot log file as described [here](https://pro.docs.ctr-electronics.com/en/latest/docs/api-reference/wpilib-integration/sysid-integration/index.html).\n- Export the AdvantageKit log file as described [here](/data-flow/sysid-compatibility).\n\n### Wheel Radius Characterization\n\nThe effective wheel radius of a robot tends to change over time as wheels are worn down, swapped, or compress into the carpet. This can have significant impacts on odometry accuracy. We recommend regularly recharacterizing wheel radius to combat these issues.\n\nWe recommend the following process to measure wheel radius:\n\n1. Place the robot on carpet. Characterizing on a hard floor may produce errors in the measurement, as the robot's effective wheel radius is affected by carpet compression.\n\n2. Using AdvantageScope, record the values of `/Drive/LeftPositionRad` and `/Drive/RightPositionRad`.\n\n3. Manually push the robot directly forward as far as possible (at least 10 feet).\n\n4. Using a tape measure, record the linear distance traveled by the robot.\n\n5. Record the new values of `/Drive/LeftPositionRad` and `/Drive/RightPositionRad`.\n\n6. The wheel radius is equal to `linear distance / wheel delta in radians`. The units of radius will match the units of the linear measurement.\n\n### Velocity PID Tuning\n\nThe project includes default gains for the velocity PID controller, which can be found in the \"Velocity PID configuration\" section of `DriveConstants.java`. These gains should be tuned for each robot.\n\n:::tip\nMore information about PID tuning can be found in the [WPILib documentation](https://docs.wpilib.org/en/stable/docs/software/advanced-controls/introduction/introduction-to-pid.html#introduction-to-pid).\n:::\n\nWe recommend using AdvantageScope to plot the measured and setpoint values while tuning. Measured values are published to the `/Drive/LeftVelocityRadPerSec` and `/Drive/RightVelocityRadPerSec` fields and setpoint values are published to the `/RealOutputs/Drive/LeftSetpointRadPerSec` and `/RealOutputs/Drive/RightSetpointRadPerSec` fields.\n\n:::info\nThe PID gains used in simulation can be tuned using the same method. **Simulation gains are stored separately from \"real\" gains in `DriveConstants.java`.**\n:::\n\n### Max Speed Measurement\n\nThe effective maximum speed of a robot is typically slightly less than the theroetically max speed based on motor free speed and gearing. To ensure that the robot remains controllable at high speeds, we recommend measuring the effective maximum speed of the robot.\n\n1. Set `maxSpeedMetersPerSec` in `DriveConstants.java` to the theoretical max speed of the robot based on motor free speed and gearing.\n\n2. Place the robot in an open space.\n\n3. Plot the measured robot speed in AdvantageScope using the `/RealOutputs/Drive/LeftVelocityMetersPerSec` and `/RealOutputs/Drive/RightVelocityMetersPerSec` fields.\n\n4. In teleop, drive forward at full speed until the robot's velocity is no longer increasing.\n\n5. Record the maximum velocity achieved and update the value of `maxSpeedMetersPerSec`.\n\n### PathPlanner Configuration\n\nThe project includes a built-in configuration for [PathPlanner](https://pathplanner.dev), located in the constructor of `Drive.java`. You may wish to manually adjust the robot mass, MOI, and wheel coefficient as configured at the bottom of `DriveConstants.java`\n\n## Customization\n\n### Custom Gyro Implementations\n\nThe project defaults to the Pigeon 2 gyro, but can be integrated with any standard gyro. An example implementation for a NavX is included.\n\nTo change the gyro implementation, switch `new GyroIOPigeon2()` in the `RobotContainer` constructor to any other implementation. For example, the `GyroIONavX` implementation is pre-configured to use a NavX connected to the MXP SPI port. See the page on [IO interfaces](/data-flow/recording-inputs/io-interfaces) for more details on how hardware abstraction works.\n\n### Custom Motor Implementations\n\nThe implementation of `ModuleIO` can be freely customized to support alternative hardware configurations, including robots without encoders. For example, the `DriveIOSpark` implementation can be customized for brushed motors by changing `MotorType.kBrushless` to `MotorType.kBrushed` and configuring the encoder counts per revolution by calling `config.encoder.countsPerRevolution(...)`.\n\n### Vision Integration\n\nThe `Drive` subsystem uses WPILib's [`DifferentialDrivePoseEstimator`](https://github.wpilib.org/allwpilib/docs/release/java/edu/wpi/first/math/estimator/DifferentialDrivePoseEstimator.html) class for odometry updates. The subsystem exposes the `addVisionMeasurement` method to enable vision systems to publish samples.\n\n:::tip\nThis project is compatible with AdvantageKit's [vision template project](./vision-template.md), which provides a starting point for implementing a pose estimation algorithm based on Limelight or PhotonVision.\n:::\n\n### Real-Time Thread Priority\n\nOptionally, the main thread can be configured to use [real-time](https://blogs.oracle.com/linux/post/task-priority) priority when running the command scheduler by removing the comments [here](https://github.com/Mechanical-Advantage/AdvantageKit/blob/a86d21b27034a36d051798e3eaef167076cd302b/template_projects/sources/diff_drive/src/main/java/frc/robot/Robot.java#L94) and [here](https://github.com/Mechanical-Advantage/AdvantageKit/blob/a86d21b27034a36d051798e3eaef167076cd302b/template_projects/sources/diff_drive/src/main/java/frc/robot/Robot.java#L104) (**IMPORTANT:** You must uncomment _both_ lines). This may improve the consistency of loop cycle timing in some cases, but should be used with caution as it will prevent other threads from running during the user code loop cycle (including internal threads required by NetworkTables, vendors, etc).\n\nThis customization **should only be used if the loop cycle time is significantly less than 20ms**, which allows other threads to continue running between user code cycles. We always recommend **thoroughly testing this change** to ensure that it does not cause unintended side effects (examples include NetworkTables lag, CAN timeouts, etc). In general, **this customization is only recommended for advanced users** who understand the potential side-effects.\n", + "content_preview": "---\nsidebar_position: 2\n---\n\n# Differential Drive Template\n\nThe differential drive template is designed for drives based on Spark Max, Talon FX, or Talon SRX controllers and a NavX, Pigeon 2, or similar gyro." + }, + { + "url": "https://docs.advantagekit.org/getting-started/template-projects/kitbot-template", + "title": "2026 KitBot Template", + "section": "Template Projects", + "language": "All", + "content": "---\nsidebar_position: 1\n---\n\n# 2026 KitBot Template\n\nThe 2026 KitBot template is designed for robots based on the design of the 2026 [FIRST KitBot](https://www.firstinspires.org/resource-library/frc/kitbot). It includes all of the features of the [differential drive template](./diff-drive-template.md), along with year-specific subsystems and a simple autonomous routine. It supports a wide variety of hardware, including Spark Max/Flex, Talon SRX, and TalonFX controllers along with the navX, Pigeon 2, and similar gyros.\n\n:::info\nThe AdvantageKit 2026 KitBot template is **open-source** and **fully customizable**:\n\n- **No black boxes:** Users can view and adjust all layers of the drive and launcher control stack.\n- **Customizable:** IO implementations can be adjusted to support any hardware configuration (see the [customization](./diff-drive-template.md#customization) section).\n- **Replayable:** Every aspect of the drive control logic, launcher control, etc. can be replayed and logged in simulation using AdvantageKit's deterministic replay features with _guaranteed accuracy_.\n\n:::\n\n## Setup\n\n1. Download the 2026 KitBot template project from the AdvantageKit release on GitHub and open it in VSCode.\n\n2. Set up the drive subsystem using the instructions found [here](./diff-drive-template.md#setup).\n\n3. In the constructor of `RobotContainer`, switch the IO implementation instantiated for other subsystems based on your chosen hardware. The default is the Talon SRX.\n\n4. In `RollerConstants`, update the device CAN ID to the correct CAN ID of the motor controller (as configured in Phoenix Tuner or REV Hardware Client)\n", + "content_preview": "---\nsidebar_position: 1\n---\n\n# 2026 KitBot Template\n\nThe 2026 KitBot template is designed for robots based on the design of the 2026 [FIRST KitBot](https://www.firstinspires.org/resource-library/frc/kitbot)." + }, + { + "url": "https://docs.advantagekit.org/getting-started/template-projects/skeleton-template", + "title": "Skeleton Template", + "section": "Template Projects", + "language": "All", + "content": "---\nsidebar_position: 6\n---\n\n# Skeleton Template\n\nThe AdvantageKit skeleton template includes the basic code required to load AdvantageKit, but no subsystems or control logic. It is intended for teams who wish to design a complete project themselves without using a more complete template.\n\n:::warning\nThe project is configured to save log files when running on a real robot. **A FAT32 formatted USB stick must be connected to one of the roboRIO USB ports to save log files.**\n:::\n\nWe recommend that most users start with one of the other provided template projects:\n\n- **[2026 KitBot Template](./kitbot-template.md)**: For robots based on the 2026 FIRST KitBot.\n- **[Differential Drive Template](./diff-drive-template.md)**: For other differential drive (tank) robots.\n- **[Spark Swerve Template](./spark-swerve-template.md)**: For swerve drives primarily using the Spark Max and Spark Flex, including NEO, NEO Vortex, or NEO 550 motors.\n- **[TalonFX Swerve Template](./talonfx-swerve-template.md)**: For swerve drives primarily using TalonFX-based motors like the Falcon 500, Kraken X60, and Kraken X44.\n- **[Vision Template](./vision-template.md)**: Example code for running simple vision targeting and pose estimation.\n", + "content_preview": "---\nsidebar_position: 6\n---\n\n# Skeleton Template\n\nThe AdvantageKit skeleton template includes the basic code required to load AdvantageKit, but no subsystems or control logic." + }, + { + "url": "https://docs.advantagekit.org/getting-started/template-projects/spark-swerve-template", + "title": "Spark Swerve Template", + "section": "Template Projects", + "language": "Java", + "content": "---\nsidebar_position: 3\n---\n\n# Spark Swerve Template\n\nAdvantageKit includes two swerve project templates with built-in support for advanced features:\n\n- [High-frequency odometry](/theory/high-frequency-odometry.md)\n- On-controller feedback loops\n- Physics simulation\n- Automated characterization routines\n- Dashboard alerts for disconnected devices\n- Pose estimator integration\n- Step-by-step setup and tuning instructions with a prebuilt AdvantageScope layout\n- **Deterministic replay** with a **guarantee of accuracy**\n\nBy default, the Spark version of the swerve template is configured for robots with **MAXSwerve modules, four NEO Vortex drive motors, four NEO 550 turn motors, four duty cycle absolute encoders, and a NavX or Pigeon 2 gyro**. See the [TalonFX(S) Swerve Template](talonfx-swerve-template.md) for swerve robots using Talon FX.\n\n:::info\nThe AdvantageKit swerve templates are **open-source** and **fully customizable**:\n\n- **No black boxes:** Users can view and adjust all layers of the swerve control stack.\n- **Customizable:** IO implementations can be adjusted to support any hardware configuration (see the [customization](#customization) section).\n- **Replayable:** Every aspect of the swerve control logic, pose estimator, etc. can be replayed and logged in simulation using AdvantageKit's deterministic replay features with _guaranteed accuracy_.\n\n:::\n\n## Setup\n\n:::tip\nThe swerve project folder includes a predefined AdvantageScope layout with tabs for each setup and tuning step described below. To open it, click `File` > `Import Layout...` in the tab bar of AdvantageScope and select the file `AdvantageScope Swerve Calibration.json` in the swerve project folder.\n:::\n\n1. Download the Spark swerve template project from the AdvantageKit release on GitHub and open it in VSCode.\n\n2. Click the WPILib icon in the VSCode toolbar and find the task `WPILib: Set Team Number`. Enter your team number and press enter.\n\n3. If not already available, download and install [Git](https://git-scm.com/downloads).\n\n4. If the project will run **only on the roboRIO 2**, uncomment lines 39-42 of `build.gradle`. These contain additional [garbage collection](https://www.geeksforgeeks.org/garbage-collection-java/) optimizations for the RIO 2 to improve performance.\n\n5. Navigate to `src/main/java/frc/robot/subsystems/drive/DriveConstants.java` in the AdvantageKit project.\n\n6. Update the values of `driveMotorReduction` and `turnMotorReduction` based on the robot's module type and configuration. This information can typically be found on the product page for the swerve module. These values represent reductions and should generally be greater than one.\n\n7. Update the values of `trackWidth` and `wheelBase` based on the distance between the left-right and front-back modules (respectively).\n\n8. Update the value of `wheelRadiusMeters` to the theoretical radius of each wheel. This value can be further refined as described in the \"Tuning\" section below.\n\n9. Update the value of `maxSpeedMetersPerSec` to the theoretical max speed of the robot. This value can be further refined as described in the \"Tuning\" section below.\n\n10. Set the value of `pigeonCanId` to the correct CAN ID of the Pigeon 2 (as configured using Tuner X). **If using a NavX instead of a Pigeon 2, see the [customization](#customization) section below.**\n\n11. For each module, set the values of `...DriveMotorId` and `...TurnMotorId` to the correct CAN IDs of the drive Spark Flex and turn Spark Max (as configured in the REV Hardware Client).\n\n12. For each module, set the value of `...ZeroRotation` to `new Rotation2d(0.0)`.\n\n13. Deploy the project to the robot and connect using AdvantageScope.\n\n14. Check that there are no dashboard alerts or errors in the Driver Station console. If any errors appear, verify that CAN IDs, firmware versions, and configurations of all devices.\n\n:::warning\nThe project is configured to save log files when running on a real robot. **A FAT32 formatted USB stick must be connected to one of the roboRIO USB ports to save log files.**\n:::\n\n15. Manually rotate the turning position of each module such that the position in AdvantageScope (`/Drive/Module.../TurnPosition`) is **increasing**. The module should be rotating **counter-clockwise** as viewed from above the robot. Verify that the units visible in AdvantageScope (radians) match the physical motion of the module. If necessary, change the value of `turnInverted`, `turnEncoderInverted`, or `turnMotorReduction`.\n\n16. Manually rotate each drive wheel and view the position in AdvantageScope (`/Drive/Module.../DrivePositionRad`). Verify that the units visible in AdvantageScope (radians) match the physical motion of the module. If necessary, change the value of `driveMotorReduction`.\n\n17. Manually rotate each module to align it directly forward. **Verify using AdvantageScope that the drive position _increases_ when the wheel rotates such that the robot would be propelled forward.** We recommend pressing a straight object such as aluminum tubing against the pairs of left and right modules to ensure accurate alignment.\n\n18. Record the value of `/Drive/Module.../TurnPosition` for each aligned module. Update the value of `...ZeroRotation` for each module to `new Rotation2d()`.\n\n## Tuning\n\n### Feedforward Characterization\n\nThe project includes default [feedforward gains](https://docs.wpilib.org/en/stable/docs/software/advanced-controls/introduction/introduction-to-feedforward.html#introduction-to-dc-motor-feedforward) for velocity control of the drive motors (`kS` and `kV`).\n\nThe project includes a simple feedforward routine that can be used to quickly measure the drive `kS` and `kV` values without requiring [SysId](https://docs.wpilib.org/en/stable/docs/software/advanced-controls/system-identification/index.html):\n\n1. Tune turning PID gains as described [here](#driveturn-pid-tuning).\n\n2. Place the robot in an open space.\n\n3. Select the \"Drive Simple FF Characterization\" auto routine.\n\n4. Enable the robot in autonomous. The robot will slowly accelerate forward, similar to a SysId quasistic test.\n\n5. Disable the robot after at least ~5-10 seconds.\n\n6. Check the console output for the measured `kS` and `kV` values, and copy them to the `driveKs` and `driveKv` constants in `DriveConstants.java`.\n\n:::info\nThe feedforward model used in simulation can be characterized using the same method. **Simulation gains are stored in the `driveSimKs` and `driveSimKv` constants.**\n:::\n\nUsers who wish to characterize acceleration gains (`kA`) can choose to use the full [SysId](https://docs.wpilib.org/en/stable/docs/software/advanced-controls/system-identification/index.html) application. The project includes auto routines for each of the four required SysId tests. Two options are available to load data in SysId:\n\n- The project is configured to use [URCL](https://docs.advantagescope.org/more-features/urcl) by default. This data can be exported as described [here](https://docs.advantagescope.org/more-features/urcl#sysid-usage).\n- Export the AdvantageKit log file as described [here](/data-flow/sysid-compatibility).\n\n:::tip\nThe built-in SysId routines can be easily adapted to characterize the turn motor feedforward or the angular motion of the robot (for example, to estimate the robot's [moment of inertia](https://sleipnirgroup.github.io/Choreo/usage/estimating-moi/)). The code below shows how the `runCharacterization` method can be adapted for these use cases.\n\n```java\n/** Characterize turn motor feedforward. */\npublic void runCharacterization(double output) {\n io.setDriveOpenLoop(0.0);\n io.setTurnOpenLoop(output);\n}\n\n/** Characterize robot angular motion. */\npublic void runCharacterization(double output) {\n io.setDriveOpenLoop(output);\n io.setTurnPosition(\n Rotation2d.fromDegrees(\n switch (index) {\n case 0 -> 135.0;\n case 1 -> 45.0;\n case 2 -> -135.0;\n case 3 -> -45.0;\n default -> 0.0;\n }));\n}\n```\n\n:::\n\n### Wheel Radius Characterization\n\nThe effective wheel radius of a robot tends to change over time as wheels are worn down, swapped, or compress into the carpet. This can have significant impacts on odometry accuracy. We recommend regularly recharacterizing wheel radius to combat these issues.\n\nThe project includes an automated wheel radius characterization routine, which only requires enough space for the robot to rotate in place.\n\n1. Place the robot on carpet. Characterizing on a hard floor may produce errors in the measurement, as the robot's effective wheel radius is affected by carpet compression.\n\n2. Select the \"Drive Wheel Radius Characterization\" auto routine.\n\n3. Enable the robot in autonomous. The robot will slowly rotate in place.\n\n4. Disable the robot after at least one full rotation.\n\n5. Check the console output for the measured wheel radius, and copy the value to `wheelRadiusMeters` in `DriveConstants.java`.\n\n### Drive/Turn PID Tuning\n\nThe project includes default gains for the drive velocity PID controllers and turn position PID controllers, which can be found in the \"Drive PID configuration\" and \"Turn PID configuration\" sections of `DriveConstants.java`. These gains should be tuned for each robot.\n\n:::tip\nMore information about PID tuning can be found in the [WPILib documentation](https://docs.wpilib.org/en/stable/docs/software/advanced-controls/introduction/introduction-to-pid.html#introduction-to-pid).\n:::\n\nWe recommend using AdvantageScope to plot the measured and setpoint values while tuning. Measured values are published to the `/RealOutputs/SwerveStates/Measured` field and setpoint values are published to the `/RealOutputs/SwerveStates/SetpointsOptimized` field.\n\n:::info\nThe PID gains used in simulation can be tuned using the same method. **Simulation gains are stored separately from \"real\" gains in `DriveConstants.java`.**\n:::\n\n### Max Speed Measurement\n\nThe effective maximum speed of a robot is typically slightly less than the theroetically max speed based on motor free speed and gearing. To ensure that the robot remains controllable at high speeds, we recommend measuring the effective maximum speed of the robot.\n\n1. Set `maxSpeedMetersPerSec` in `DriveConstants.java` to the theoretical max speed of the robot based on motor free speed and gearing. This value can typically be found on the product page for your chosen swerve modules.\n\n2. Place the robot in a open space.\n\n3. Plot the measured robot speed in AdvantageScope using the `/RealOutputs/SwerveChassisSpeeds/Measured` field.\n\n4. In teleop, drive forwards at full speed until the robot velocity is no longer increasing.\n\n5. Record the maximum velocity achieved and update the value of `maxSpeedMetersPerSec`.\n\n### Slip Current Measurement\n\nThe value of `driveMotorCurrentLimit` can be tuned to avoid slipping the wheels.\n\n1. Place the robot against the solid wall.\n\n2. Using AdvantageScope, plot the current of a drive motor from the `/Drive/Module.../DriveCurrentAmps` key, and the velocity of the motor from the `/Drive/Module.../DriveVelocityRadPerSec` key.\n\n3. Accelerate forward until the drive velocity increases (the wheel slips). Note the current at this time.\n\n4. Update the value of `driveMotorCurrentLimit` to this value.\n\n### PathPlanner Configuration\n\nThe project includes a built-in configuration for [PathPlanner](https://pathplanner.dev), located in the constructor of `Drive.java`. You may wish to manually adjust the following values:\n\n- Robot mass, MOI, and wheel coefficient as configured at the bottom of `DriveConstants.java`\n- Drive PID constants as configured in `AutoBuilder`.\n- Turn PID constants as configured in `AutoBuilder`.\n\n## Customization\n\n### Setting Odometry Frequency\n\nBy default, the project runs at **100Hz**. This value is stored as `odometryFrequency` at the top of `DriveConstants.java` and can be changed. The project configures all devices to minimize CAN bus utilization, but we recommend monitoring utilization carefully when increasing frequency.\n\n### Switching Between Spark Max and Flex\n\nSwitching between the Spark Max and Spark Flex for drive and turn motors is very simple. In the constructor of `ModuleIOSpark`, change the call instantiating the Spark object to use `CANSparkMax` or `CANSparkFlex`. The configuration object must also be changed to the corresponding `SparkMaxConfig` or `SparkFlexConfig` class.\n\nWhen switching between motor types, the `driveGearbox` and `turnGearbox` constants in `DriveConstants` should be updated accordingly.\n\n### Custom Gyro Implementations\n\nThe project defaults to the Pigeon 2 gyro, but can be integrated with any standard gyro. An example implementation for a NavX is included.\n\nTo change the gyro implementation, switch `new GyroIOPigeon2()` in the `RobotContainer` constructor to any other implementation. For example, the `GyroIONavX` implementation is pre-configured to use a NavX connected to the MXP SPI port. See the page on [IO interfaces](/data-flow/recording-inputs/io-interfaces) for more details on how hardware abstraction works.\n\nThe `SparkOdometryThread` class reads high-frequency gyro data for odometry alongside samples from drive encoders. This class supports both Spark devices and generic signals. Note that the gyro should be configured to publish signals at the same frequency as odometry. Call `registerSignal` with a double supplier to create a queue, as shown in the `GyroIONavX` implementation:\n\n```java\nQueue yawPositionQueue = SparkOdometryThread.getInstance().registerSignal(navX::getAngle);\n```\n\n:::info\nReference the full `GyroIONavX` implementation for an example of how to create a timestamp queue and update the odometry inputs for the gyro.\n:::\n\n### Custom Module Implementations\n\nThe implementation of `ModuleIOSpark` can be freely customized to support alternative hardware configurations, such as using a TalonFX(S)-based drive motor. When integrating with TalonFX(S) devices, we recommend referencing the implementation found in the `ModuleIOTalonFX` or `ModuleIOTalonFXS` classes of the [TalonFX(S) Swerve Template](talonfx-swerve-template.md).\n\nAs described in the previous section, the `SparkOdometryThread` supports non-Spark signals through the `registerSignal` method. This allows devices from different vendors to be freely mixed.\n\nBy default, the project uses a duty cycle encoder connected to a turn Spark Max. When using another absolute encoder (such as a CANcoder or HELIUM Canandmag), we recommend resetting the relative encoder based on the absolute encoder; the relative encoder can then be used for PID control. In this case, the following changes are required:\n\n1. Create the encoder object in `ModuleIOSpark` and configure it appropriately.\n\n2. Change the feedback sensor source of the turn controller:\n\n```java\nturnEncoder = turnSpark.getEncoder(); // Change the type of turnEncoder to RelativeEncoder\nturnConfig.closedLoopConfig.feedbackSensor(FeedbackSensor.kPrimaryEncoder); // Was: kAbsoluteEncoder\n```\n\n3. Replace `turnConfig.absoluteEncoder...` with `turnConfig.encoder...`, and `averageDepth` with `uvwAverageDepth`. Remove the setter for `inverted(...)`.\n\n4. In the signal config for the turn motor, change all instances of `absoluteEncoderPosition...` and `absoluteEncoderVelocity...` to `primaryEncoderPosition...` and `primaryEncoderVelocity...`.\n\n5. Incorporate the turn motor reduction in the encoder position and velocity factors:\n\n```java\npublic static final double turnEncoderPositionFactor = 2 * Math.PI / turnMotorReduction; // Rotor Rotations -> Wheel Radians\npublic static final double turnEncoderVelocityFactor = (2 * Math.PI) / 60.0 / turnMotorReduction; // Rotor RPM -> Wheel Rad/Sec\n```\n\n6. Reset the relative encoder position at startup:\n\n```java\ntryUntilOk(turnSpark, 5, () -> turnEncoder.setPosition(customEncoder.getPositionRadians()));\n```\n\n### Vision Integration\n\nThe `Drive` subsystem uses WPILib's [`SwerveDrivePoseEstimator`](https://github.wpilib.org/allwpilib/docs/release/java/edu/wpi/first/math/estimator/SwerveDrivePoseEstimator.html) class for odometry updates. The subsystem exposes the `addVisionMeasurement` method to enable vision systems to publish samples.\n\n:::tip\nThis project is compatible with AdvantageKit's [vision template project](./vision-template.md), which provides a starting point for implementing a pose estimation algorithm based on Limelight or PhotonVision.\n:::\n\n### Swerve Setpoint Generator\n\nThe project already includes basic mechanisms to reduce skidding, such as drive current limits and cosine optimization. Users who prefer more control over module skidding may wish to utilize Team 254's swerve setpoint generator. Documentation for using the version of this algorithm bundled with PathPlanner can be found [here](https://pathplanner.dev/pplib-swerve-setpoint-generator.html). The `SwerveSetpointGenerator` should be instantiated in the `Drive` subsystem and used in the `runVelocity` method, as shown below:\n\n```java\nprivate final SwerveSetpointGenerator setpointGenerator;\nprivate SwerveSetpoint previousSetpoint;\n\npublic Drive(...) {\n // ...\n\n setpointGenerator = new SwerveSetpointGenerator(...);\n previousSetpoint = new SwerveSetpoint(getChassisSpeeds(), getModuleStates(), DriveFeedforwards.zeroes(4));\n\n // ...\n}\n\npublic void runVelocity(ChassisSpeeds speeds) {\n previousSetpoint = setpointGenerator.generateSetpoint(previousSetpoint, speeds, 0.02);\n SwerveModuleStatep[] setpointStates = previousSetpoint.moduleStates();\n\n // ...\n}\n```\n\n### Advanced Physics Simulation\n\nThe project can be easily adapted to utilize Team 5516's [maple-sim](https://github.com/Shenzhen-Robotics-Alliance/Maple-Sim) library for simulation, which provides a full rigid-body simulation of the swerve drive and its interactions with the field. Check the documentation for more details on how to install and use the library.\n\n### Real-Time Thread Priority\n\nOptionally, the main thread can be configured to use [real-time](https://blogs.oracle.com/linux/post/task-priority) priority when running the command scheduler by removing the comments [here](https://github.com/Mechanical-Advantage/AdvantageKit/blob/a86d21b27034a36d051798e3eaef167076cd302b/template_projects/sources/spark_swerve/src/main/java/frc/robot/Robot.java#L94) and [here](https://github.com/Mechanical-Advantage/AdvantageKit/blob/a86d21b27034a36d051798e3eaef167076cd302b/template_projects/sources/spark_swerve/src/main/java/frc/robot/Robot.java#L104) (**IMPORTANT:** You must uncomment _both_ lines). This may improve the consistency of loop cycle timing in some cases, but should be used with caution as it will prevent other threads from running during the user code loop cycle (including internal threads required by NetworkTables, vendors, etc).\n\nThis customization **should only be used if the loop cycle time is significantly less than 20ms**, which allows other threads to continue running between user code cycles. We always recommend **thoroughly testing this change** to ensure that it does not cause unintended side effects (examples include NetworkTables lag, CAN timeouts, etc). In general, **this customization is only recommended for advanced users** who understand the potential side-effects.\n", + "content_preview": "---\nsidebar_position: 3\n---\n\n# Spark Swerve Template\n\nAdvantageKit includes two swerve project templates with built-in support for advanced features:\n\n- [High-frequency odometry](/theory/high-frequency-odometry.md)\n- On-controller feedback loops\n- Physics simulation\n- Automated characterization..." + }, + { + "url": "https://docs.advantagekit.org/getting-started/template-projects/talonfx-swerve-template", + "title": "TalonFX(S) Swerve Template", + "section": "Template Projects", + "language": "Java", + "content": "---\nsidebar_position: 4\n---\n\nimport Tabs from '@theme/Tabs';\nimport TabItem from '@theme/TabItem';\n\n# TalonFX(S) Swerve Template\n\nAdvantageKit includes two swerve project templates with built-in support for advanced features:\n\n- Easy setup with Tuner X swerve project generator\n- [High-frequency odometry](/theory/high-frequency-odometry.md)\n- CANivore time sync (for Phoenix Pro users)\n- On-controller feedback loops\n- Physics simulation\n- Automated characterization routines\n- Dashboard alerts for disconnected devices\n- Pose estimator integration using standard FPGA timestamps\n- Step-by-step setup and tuning instructions with a prebuilt AdvantageScope layout\n- **Deterministic replay** with a **guarantee of accuracy**\n\nBy default, the TalonFX(S) version of the swerve template is configured for robots with **four TalonFX drive motors, four TalonFX turn motors, four CANcoders, and a NavX or Pigeon 2 gyro**. An alternative IO implementation is provided for robots with **four TalonFXS drive motors, four TalonFXS turn motors, and four PWM encoders connected to CANdis**. These implementations can be freely mixed to support [alternative hardware configurations](#custom-module-implementations). Also see the [Spark Swerve Template](spark-swerve-template.md) for swerve robots using Spark Max/Flex.\n\n:::info\nThe AdvantageKit swerve templates are **open-source** and **fully customizable**:\n\n- **No black boxes:** Users can view and adjust all layers of the swerve control stack.\n- **Customizable:** IO implementations can be adjusted to support any hardware configuration (see the [customization](#customization) section).\n- **Replayable:** Every aspect of the swerve control logic, pose estimator, etc. can be replayed and logged in simulation using AdvantageKit's deterministic replay features with _guaranteed accuracy_.\n\n:::\n\n## Setup\n\n:::tip\nThe swerve project folder includes a predefined AdvantageScope layout with tabs for each setup and tuning step described below. To open it, click `File` > `Import Layout...` in the tab bar of AdvantageScope and select the file `AdvantageScope Swerve Calibration.json` in the swerve project folder.\n:::\n\n\n\n\n:::danger\nCTRE only permits the swerve project generator to be used on swerve robots with **exclusively CTRE hardware** (including a Pigeon 2). Otherwise, switch to the \"Manual\" tab for standard setup instructions.\n:::\n\n1. Download the TalonFX swerve template project from the AdvantageKit release on GitHub and open it in VSCode.\n\n2. Click the WPILib icon in the VSCode toolbar and find the task `WPILib: Set Team Number`. Enter your team number and press enter.\n\n3. If not already available, download and install [Git](https://git-scm.com/downloads).\n\n4. If the project will run **only on the roboRIO 2**, uncomment lines 39-42 of `build.gradle`. These contain additional [garbage collection](https://www.geeksforgeeks.org/garbage-collection-java/) optimizations for the RIO 2 to improve performance.\n\n5. Follow the instructions in the Phoenix documentation for the [Tuner X Swerve Project Generator](https://v6.docs.ctr-electronics.com/en/latest/docs/tuner/tuner-swerve/index.html).\n\n6. On the final screen in Tuner X, choose \"Generate only TunerConstants\" and overwrite the file located at `src/main/java/frc/robot/generated/TunerConstants.java`.\n\n7. In `TunerConstants.java`, comment out the [last import](https://github.com/CrossTheRoadElec/Phoenix6-Examples/blob/88be410fdbfd811e6f776197d41c0bea5f109b0e/java/SwerveWithPathPlanner/src/main/java/frc/robot/generated/TunerConstants.java#L17) and [last method](https://github.com/CrossTheRoadElec/Phoenix6-Examples/blob/88be410fdbfd811e6f776197d41c0bea5f109b0e/java/SwerveWithPathPlanner/src/main/java/frc/robot/generated/TunerConstants.java#L198-L202). Before removing them, both lines will be marked as errors in VSCode.\n\n8. In `TunerConstants.java`, change `kSteerInertia` to 0.004 and `kDriveInertia` to 0.025.\n\n9. If the robot does not use the default arrangement of 8 TalonFXs and 4 CANcoders, please see the section [here](#custom-module-implementations) on alternative module IO implementations.\n\n:::warning\nThe project is configured to save log files when running on a real robot. **A FAT32 formatted USB stick must be connected to one of the roboRIO USB ports to save log files.**\n:::\n\n\n\n\n1. Download the TalonFX swerve template project from the AdvantageKit release on GitHub and open it in VSCode.\n\n2. Click the WPILib icon in the VSCode toolbar and find the task `WPILib: Set Team Number`. Enter your team number and press enter.\n\n3. If not already available, download and install [Git](https://git-scm.com/downloads).\n\n4. If the project will run **only on the roboRIO 2**, uncomment lines 39-42 of `build.gradle`. These contain additional [garbage collection](https://www.geeksforgeeks.org/garbage-collection-java/) optimizations for the RIO 2 to improve performance.\n\n5. Navigate to `src/main/java/frc/robot/generated/TunerConstants.java` in the AdvantageKit project.\n\n6. Update the values of `kDriveGearRatio` and `kSteerGearRatio` based on the robot's module type and configuration. This information can typically be found on the product page for the swerve module. These values represent reductions and should generally be greater than one.\n\n7. Update the value of `kWheelRadius` to the theoretical radius of each wheel. This value can be further refined as described in the \"Tuning\" section below.\n\n8. Update the value of `kSpeedAt12Volts` to the theoretical max speed of the robot. This value can be further refined as described in the \"Tuning\" section below.\n\n9. Update the value of `kCANBus` based on the CAN bus used by the drive devices. Check the [`CANBus`](https://api.ctr-electronics.com/phoenix6/latest/java/com/ctre/phoenix6/CANBus.html) API documentation for details on possible values.\n\n10. Set the value of `kPigeonId` to the correct CAN ID of the Pigeon 2 (as configured using Tuner X). **If using a NavX instead of a Pigeon 2, see the [customization](#customization) section below.**\n\n11. For each module, set the values of `k...DriveMotorId`, `k...SteerMotorId`, and `k...EncoderId` to the correct CAN IDs of the drive TalonFX(S), turn TalonFX(S), and CANcoder/CANdi (as configured in Tuner X).\n\n12. For each module, set the values of `k...XPos` and `k...YPos` based on the distance from each module to the center of the robot. Positive X values are closer to the front of the robot and positive Y values are closer to the left side of the robot.\n\n13. For each module, set the value of `k...EncoderOffset` to `Radians.of(0.0)`.\n\n14. Deploy the project to the robot and connect using AdvantageScope.\n\n15. Check that there are no dashboard alerts or errors in the Driver Station console. If any errors appear, verify tha CAN IDs, firmware versions, and configurations of all devices.\n\n:::warning\nThe project is configured to save log files when running on a real robot. **A FAT32 formatted USB stick must be connected to one of the roboRIO USB ports to save log files.**\n:::\n\n16. Manually rotate the turning position of each module such that the position in AdvantageScope (`/Drive/Module.../TurnPosition`) is **increasing**. The module should be rotating **counter-clockwise** as viewed from above the robot. Verify that the units visible in AdvantageScope (radians) match the physical motion of the module. If necessary, change the value of `k...SteerMotorInverted` or `kSteerGearRatio`.\n\n17. Manually rotate each drive wheel and view the position in AdvantageScope (`/Drive/Module.../DrivePositionRad`). Verify that the units visible in AdvantageScope (radians) match the physical motion of the module. If necessary, change the value of `kDriveGearRatio`.\n\n18. Manually rotate each module to align it directly forward. **Verify using AdvantageScope that the drive position _increases_ when the wheel rotates such that the robot would be propelled forward.** We recommend pressing a straight object such as aluminum tubing against the pairs of left and right modules to ensure accurate alignment.\n\n19. Record the value of `/Drive/Module.../TurnPosition` for each aligned module. Update the value of `k...EncoderOffset` for each module to `Radians.of()`. **The value saved in `TunerConstants` must be the _negative_ of the value displayed in AdvantageScope (i.e. positive values become negative and vice versa).**\n\n20. If the robot does not use the default arrangement of 8 TalonFXs and 4 CANcoders, please see the section [here](#custom-module-implementations) on alternative module IO implementations. If the robot does not use a Pigeon 2, please see the section [here](#custom-gyro-implementations) on alternative gyro options.\n\n\n\n\n## Tuning\n\n### Torque-Current Control\n\nThe project defaults to voltage control for both the drive and turn motors. Phoenix Pro subscribers can optionally switch to torque-current control, as described in the [Phoenix documentation](https://pro.docs.ctr-electronics.com/en/latest/docs/api-reference/device-specific/talonfx/talonfx-control-intro.html#torquecurrentfoc). This can be configured by changing the values of `kSteerClosedLoopOutput` and/or `kDriveClosedLoopOutput` in `TunerConstants.java` to `ClosedLoopOutputType.TorqueCurrentFOC`.\n\n:::info\nTorque-current control requires different gains than voltage control. We recommend following the steps below to tune feedforward and PID gains.\n:::\n\n:::warning\nCTRE does not allow torque-current control on the TalonFXS.\n:::\n\n### Feedforward Characterization\n\nThe project includes default [feedforward gains](https://docs.wpilib.org/en/stable/docs/software/advanced-controls/introduction/introduction-to-feedforward.html#introduction-to-dc-motor-feedforward) for velocity control of the drive motors (`kS` and `kV`), acceleration control of the drive motors (`kA`), and velocity control of the turn motors (`kS` and `kV`).\n\n:::info\nThe AdvantageKit template requires different feedforward gains than CTRE's default swerve code, because it applies the swerve gear ratio using the TalonFX(S) firmware and not on the RIO.\n:::\n\n:::tip\nThe drive `kS` and `kV` gains should **always** be characterized (as described below). The drive/turn `kA` gains and turn `kS` and `kV` gains are unnecessary in most cases, but can be tuned by advanced users.\n:::\n\nThe project includes a simple feedforward routine that can be used to quickly measure the drive `kS` and `kV` values without requiring [SysId](https://docs.wpilib.org/en/stable/docs/software/advanced-controls/system-identification/index.html):\n\n1. Tune turning PID gains as described [here](#driveturn-pid-tuning).\n\n2. Place the robot in an open space.\n\n3. Select the \"Drive Simple FF Characterization\" auto routine.\n\n4. Enable the robot in autonomous. The robot will slowly accelerate forwards, similar to a SysId quasistic test.\n\n5. Disable the robot after at least ~5-10 seconds.\n\n6. Check the console output for the measured `kS` and `kV` values, and copy them to the `driveGains` config in `TunerConstants.java`.\n\n:::info\nThe feedforward model used in simulation can be characterized using the same method. **Simulation gains are stored in `ModuleIOSim.java` instead of `TunerConstants.java`.**\n:::\n\nUsers who wish to characterize acceleration gains (`kA`) or turn gains can choose to use the full [SysId](https://docs.wpilib.org/en/stable/docs/software/advanced-controls/system-identification/index.html) application. The project includes auto routines for each of the four required SysId tests. Two options are available to load data in SysId:\n\n- Export the Hoot log file as described [here](https://pro.docs.ctr-electronics.com/en/latest/docs/api-reference/wpilib-integration/sysid-integration/index.html).\n- Export the AdvantageKit log file as described [here](/data-flow/sysid-compatibility). Note that AdvantageKit values are logged in radians while Phoenix requires rotations to be used. Gains must be converted appropriately.\n\n:::tip\nThe built-in SysId routines can be easily adapted to characterize the turn motor feedforward or the angular motion of the robot (for example, to estimate the robot's [moment of inertia](https://sleipnirgroup.github.io/Choreo/usage/estimating-moi/)). The code below shows how the `runCharacterization` method can be adapted for these use cases.\n\n```java\n/** Characterize turn motor feedforward. */\npublic void runCharacterization(double output) {\n io.setDriveOpenLoop(0.0);\n io.setTurnOpenLoop(output);\n}\n\n/** Characterize robot angular motion. */\npublic void runCharacterization(double output) {\n io.setDriveOpenLoop(output);\n io.setTurnPosition(new Rotation2d(constants.LocationX, constants.LocationY).plus(Rotation2d.kCCW_Pi_2));\n}\n```\n\n:::\n\n### Wheel Radius Characterization\n\nThe effective wheel radius of a robot tends to change over time as wheels are worn down, swapped, or compress into the carpet. This can have significant impacts on odometry accuracy. We recommend regularly recharacterizing wheel radius to combat these issues.\n\nThe project includes an automated wheel radius characterization routine, which only requires enough space for the robot to rotate in place.\n\n1. Place the robot on carpet. Characterizing on a hard floor may produce errors in the measurement, as the robot's effective wheel radius is affected by carpet compression.\n\n2. Select the \"Drive Wheel Radius Characterization\" auto routine.\n\n3. Enable the robot in autonomous. The robot will slowly rotate in place.\n\n4. Disable the robot after at least one full rotation.\n\n5. Check the console output for the measured wheel radius, and copy the value to `kWheelRadius` in `TunerConstants.java`.\n\n### Drive/Turn PID Tuning\n\nThe project includes default gains for the drive velocity PID controllers and turn position PID controllers, which can be found in the `steerGains` and `driveGains` configs in `TunerConstants.java`. These gains should be tuned for each robot.\n\n:::info\nThe AdvantageKit template requires different PID gains than CTRE's default swerve code, because it applies the swerve gear ratio using the TalonFX(S) firmware and not on the RIO.\n:::\n\n:::tip\nMore information about PID tuning can be found in the [WPILib documentation](https://docs.wpilib.org/en/stable/docs/software/advanced-controls/introduction/introduction-to-pid.html#introduction-to-pid).\n:::\n\nWe recommend using AdvantageScope to plot the measured and setpoint values while tuning. Measured values are published to the `/RealOutputs/SwerveStates/Measured` field and setpoint values are published to the `/RealOutputs/SwerveStates/SetpointsOptimized` field.\n\n:::info\nThe PID gains used in simulation can be tuned using the same method. **Simulation gains are stored in `ModuleIOSim.java` instead of `TunerConstants.java`.**\n:::\n\n### Max Speed Measurement\n\nThe effective maximum speed of a robot is typically slightly less than the theroetically max speed based on motor free speed and gearing. To ensure that the robot remains controllable at high speeds, we recommend measuring the effective maximum speed of the robot.\n\n1. Set `kSpeedAt12Volts` in `TunerConstants.java` to the theoretical max speed of the robot based on motor free speed and gearing. This value can typically be found on the product page for your chosen swerve modules.\n\n2. Place the robot in an open space.\n\n3. Plot the measured robot speed in AdvantageScope using the `/RealOutputs/SwerveChassisSpeeds/Measured` field.\n\n4. In teleop, drive forward at full speed until the robot's velocity is no longer increasing.\n\n5. Record the maximum velocity achieved and update the value of `kSpeedAt12Volts`.\n\n### Slip Current Measurement\n\nThe value of `kSlipCurrent` can be tuned to avoid slipping the wheels.\n\n1. Place the robot against the solid wall.\n\n2. Using AdvantageScope, plot the current of a drive motor from the `/Drive/Module.../DriveCurrentAmps` key, and the velocity of the motor from the `/Drive/Module.../DriveVelocityRadPerSec` key.\n\n3. Accelerate forward until the drive velocity increases (the wheel slips). Note the current at this time.\n\n4. Update the value of `kSlipCurrent` to this value.\n\n### PathPlanner Configuration\n\nThe project includes a built-in configuration for [PathPlanner](https://pathplanner.dev), located in the constructor of `Drive.java`. You may wish to manually adjust the following values:\n\n- Robot mass, MOI, and wheel coefficient as configured at the top of `Drive.java`\n- Drive PID constants as configured in `AutoBuilder`.\n- Turn PID constants as configured in `AutoBuilder`.\n\n## Customization\n\n### Setting Odometry Frequency\n\nBy default, the project runs at **100Hz** on the RIO CAN bus and **250Hz** on CAN FD buses. These values are stored at the top of `Drive.java` and can be freely customized. The project configures all devices to minimize CAN bus utilization, but we recommend monitoring utilization carefully when increasing frequency.\n\n### Custom Gyro Implementations\n\nThe project defaults to the Pigeon 2 gyro, but can be integrated with any standard gyro. An example implementation for a NavX is included.\n\nTo change the gyro implementation, switch `new GyroIOPigeon2()` in the `RobotContainer` constructor to any other implementation. For example, the `GyroIONavX` implementation is pre-configured to use a NavX connected to the MXP SPI port. See the page on [IO interfaces](/data-flow/recording-inputs/io-interfaces) for more details on how hardware abstraction works.\n\nThe `PhoenixOdometryThread` class reads high-frequency gyro data for odometry alongside samples from drive encoders. This class supports both Phoenix signals and generic signals. Note that the gyro should be configured to publish signals at the same frequency as odometry. Call `registerSignal` with a double supplier to create a queue, as shown in the `GyroIONavX` implementation:\n\n```java\nQueue yawPositionQueue = PhoenixOdometryThread.getInstance().registerSignal(navX::getAngle);\n```\n\n:::info\nReference the full `GyroIONavX` implementation for an example of how to create a timestamp queue and update the odometry inputs for the gyro.\n:::\n\n### Custom Module Implementations\n\nThe template project includes multiple IO implementations for different hardware arrangements, as listed below. The selected IO implementation can be changed in `RobotContainer`.\n\n- **`ModuleIOTalonFX`**: TalonFX drive controllers, TalonFX turn controllers, and CANcoders (default)\n- **`ModuleIOTalonFXS`**: TalonFXS drive controllers, TalonFXS turn controllers, and CANdis\n\nThe implementations of `ModuleIOTalonFX` and `ModuleIOTalonFXS` can be freely customized to support alternative hardware configurations, such as mixing and matching the TalonFX/TalonFXS, using an alternative encoder, or using a Spark Max/Flex instead of a CTRE controller. **We recommend copying any configuration for alternative devices directly from an existing IO implementation whenever possible** (e.g. copying the CANcoder configuration from `ModuleIOTalonFX` to `ModuleIOTalonFXS`). When integrating with Spark devices, please see the example IO implementation in the `ModuleIOSpark` class of the [Spark Swerve Template](spark-swerve-template.md).\n\nAs described in the previous section, the `PhoenixOdometryThread` supports non-Phoenix signals through the `registerSignal` method. This allows devices from different vendors to be freely mixed.\n\nBy default, the project uses a CANcoder/CANdi in remote/fused/sync mode. When using another absolute encoder (such as a duty cycle encoder or HELIUM Canandmag), we recommend reseting the relative encoder based on the absolute encoder; the relative encoder can then be used for PID control. In this case, the following changes are required:\n\n1. Create the encoder object in `ModuleIOTalonFX` and configure it appropriately.\n\n2. Change the feedback sensor source of the turn controller:\n\n```java\nturnConfig.Feedback.FeedbackSensorSource = FeedbackSensorSourceValue.RotorSensor;\n```\n\n3. Replace `RotorToSensorRatio` with `SensorToMechanismRatio` as shown below:\n\n```java\nturnConfig.Feedback.SensorToMechanismRatio = constants.SteerMotorGearRatio;\n```\n\n4. Reset the relative encoder position at startup:\n\n```java\ntryUntilOk(5, () -> turnTalon.setPosition(customEncoder.getPositionRotations(), 0.25));\n```\n\n### Profiled Turning PID\n\nBy default, the project uses standard PID controllers for turn control. Users may choose to replace the standard control request with [Motion Magic](https://pro.docs.ctr-electronics.com/en/latest/docs/api-reference/device-specific/talonfx/motion-magic.html#motion-magic) or [Motion Magic Expo](https://pro.docs.ctr-electronics.com/en/latest/docs/api-reference/device-specific/talonfx/motion-magic.html#motion-magic-expo) control requests. To implement this, simply replace the position request in `ModuleIOTalonFX` with the new request type, as shown below. The Motion Magic constraints are already configured in the `ModuleIOTalonFX` constructor, but can be adjusted.\n\n```java\nprivate final MotionMagicVoltage positionVoltageRequest = new MotionMagicVoltage(0.0);\nprivate final MotionMagicTorqueCurrentFOC positionTorqueCurrentRequest = new MotionMagicTorqueCurrentFOC(0.0);\n```\n\n### Vision Integration\n\nThe `Drive` subsystem uses WPILib's [`SwerveDrivePoseEstimator`](https://github.wpilib.org/allwpilib/docs/release/java/edu/wpi/first/math/estimator/SwerveDrivePoseEstimator.html) class for odometry updates. The subsystem exposes the `addVisionMeasurement` method to enable vision systems to publish samples.\n\nUsers migrating from CTRE's swerve library should note that the AdvantageKit template uses **standard FPGA timestamps** for pose estimation rather than CTRE's \"current time.\" This means that pose estimates from Limelight or PhotonVision can be passed _directly_ to the pose estimator without needing to call [`Utils.fpgaToCurrentTime`]().\n\n:::tip\nThis project is compatible with AdvantageKit's [vision template project](./vision-template.md), which provides a starting point for implementing a pose estimation algorithm based on Limelight or PhotonVision.\n:::\n\n### Swerve Setpoint Generator\n\nThe project already includes basic mechanisms to reduce skidding, such as drive current limits and cosine optimization. Users who prefer more control over module skidding may wish to utilize Team 254's swerve setpoint generator. Documentation for using the version of this algorithm bundled with PathPlanner can be found [here](https://pathplanner.dev/pplib-swerve-setpoint-generator.html). The `SwerveSetpointGenerator` should be instantiated in the `Drive` subsystem and used in the `runVelocity` method, as shown below:\n\n```java\nprivate final SwerveSetpointGenerator setpointGenerator;\nprivate SwerveSetpoint previousSetpoint;\n\npublic Drive(...) {\n // ...\n\n setpointGenerator = new SwerveSetpointGenerator(...);\n previousSetpoint = new SwerveSetpoint(getChassisSpeeds(), getModuleStates(), DriveFeedforwards.zeroes(4));\n\n // ...\n}\n\npublic void runVelocity(ChassisSpeeds speeds) {\n previousSetpoint = setpointGenerator.generateSetpoint(previousSetpoint, speeds, 0.02);\n SwerveModuleStatep[] setpointStates = previousSetpoint.moduleStates();\n\n // ...\n}\n```\n\n### Advanced Physics Simulation\n\nThe project can be easily adapted to utilize Team 5516's [maple-sim](https://github.com/Shenzhen-Robotics-Alliance/Maple-Sim) library for simulation, which provides a full rigid-body simulation of the swerve drive and its interactions with the field. Check the documentation for more details on how to install and use the library.\n\n### Real-Time Thread Priority\n\nOptionally, the main thread can be configured to use [real-time](https://blogs.oracle.com/linux/post/task-priority) priority when running the command scheduler by removing the comments [here](https://github.com/Mechanical-Advantage/AdvantageKit/blob/a86d21b27034a36d051798e3eaef167076cd302b/template_projects/sources/talonfx_swerve/src/main/java/frc/robot/Robot.java#L110) and [here](https://github.com/Mechanical-Advantage/AdvantageKit/blob/a86d21b27034a36d051798e3eaef167076cd302b/template_projects/sources/talonfx_swerve/src/main/java/frc/robot/Robot.java#L120) (**IMPORTANT:** You must uncomment _both_ lines). This may improve the consistency of loop cycle timing in some cases, but should be used with caution as it will prevent other threads from running during the user code loop cycle (including internal threads required by NetworkTables, vendors, etc).\n\nThis customization **should only be used if the loop cycle time is significantly less than 20ms**, which allows other threads to continue running between user code cycles. We always recommend **thoroughly testing this change** to ensure that it does not cause unintended side effects (examples include NetworkTables lag, CAN timeouts, etc). In general, **this customization is only recommended for advanced users** who understand the potential side-effects.\n", + "content_preview": "---\nsidebar_position: 4\n---\n\nimport Tabs from '@theme/Tabs';\nimport TabItem from '@theme/TabItem';\n\n# TalonFX(S) Swerve Template\n\nAdvantageKit includes two swerve project templates with built-in support for advanced features:\n\n- Easy setup with Tuner X swerve project generator\n- [High-frequency..." + }, + { + "url": "https://docs.advantagekit.org/getting-started/template-projects/vision-template", + "title": "Vision Template", + "section": "Template Projects", + "language": "All", + "content": "---\nsidebar_position: 5\n---\n\n# Vision Template\n\nThe vision template project provides a starting point for creating a high-performance vision or pose estimation system compatible with AdvantageKit's deterministic log replay. It includes support for the following features:\n\n- Integration with both Limelight and PhotonVision\n- Vision simulation via PhotonLib\n- Options for both simple targeting and full pose estimation\n- High-frequency sampling to ensure that every observation is processed (and never duplicated)\n- Efficient logging of observations via structs\n- Advanced filtering options, including automatic scaling of standard deviations\n- Detailed logging of filters, allowing for easy tuning in replay\n- **Deterministic replay** with a **guarantee of accuracy**\n\n:::info\nThe AdvantageKit vision template is **open-source** and **fully customizable**:\n\n- **No black boxes:** Users can view and adjust all layers of the vision processing stack.\n- **Customizable:** IO implementations can be adjusted to support any hardware configuration.\n- **Replayable:** Every aspect of the vision processing and filtering logic can be replayed and logged in simulation using AdvantageKit's deterministic replay features with _guaranteed accuracy_.\n\n:::\n\n## โš ๏ธ Warning\n\nThis project is provided as a **starting point** that will work reasonably well across a variety of situations, but **must be customized to fit your specific needs**. It is intended as a **platform** on top of which more optimized systems can be designed. The best pose estimation systems account for a wide variety of factors, including:\n\n- Game design\n- Robot design\n- Strategic objectives\n- Field tolerances\n- ...and more!\n\nHigh-quality pose estimation requires frequent iteration of all aspects of the control stack to address these factors, including **cameras, mounts, coprocessors, calibrations, pipelines, communication, and filtering**. This project provides a starting point for only one small part of a well-optimized vision stack.\n\n## Configuration\n\nThe project is primarily configured via the `VisionConstants` class, with comments explaining the purpose of each field. The selected vision implementation can be changed in the constructor of `RobotContainer`.\n\n:::tip\nIn addition to pose estimation, this project include an example of simple targeting with AprilTags. For many games and robots, this is a significantly simpler method of accomplishing game objectives. Check the `getTargetX` method of `Vision` and `configureButtonBindings` method of `RobotContainer` for details.\n:::\n\n### Logging\n\nThe vision subsystem logs a large set of outputs that can be used for debugging and tuning. Each camera logs the following fields:\n\n- `TagPoses`: A list of 3D poses representing the set of visible tags. We recommend visualizing this field using the \"Vision Target\" object on the 3D field tab in AdvantageScope.\n- `RobotPoses`: A list of 3D poses representing the raw pose estimates from the last cycle. We recommend visualizing this field using the \"Ghost\" object on the 3D field tab in AdvantageScope.\n- `RobotPosesAccepted`: A subset of the `RobotPoses` list with the set of estimates that passed all stages of filtering.\n- `RobotPosesRejected`: A subset of the `RobotPoses` list with the set of estimates that were removed during filtering.\n\nThe `Summary` table includes identical fields which include samples from every camera.\n\n### Limelight 4\n\nThis project is compatible with all variants of Limelight by default (in addition to PhotonVision). **Limelight 4** users who wish to take advantage of the built-in IMU for MegaTag 2 should check the [Limelight documentation](https://docs.limelightvision.io/docs/docs-limelight/pipeline-apriltag/apriltag-robot-localization-megatag2#using-limelight-4s-built-in-imu-with-imumode_set--setimumode) for details. Note that the template already publishes the robot orientation every loop cycle.\n\n:::info\nUsers can configure the IMU mode by importing [LimelightLib](https://docs.limelightvision.io/docs/docs-limelight/apis/limelight-lib) or by publishing an integer to the `imumode_set` key in NetworkTables.\n:::\n\n### Real-Time Thread Priority\n\nOptionally, the main thread can be configured to use [real-time](https://blogs.oracle.com/linux/post/task-priority) priority when running the command scheduler by removing the comments [here](https://github.com/Mechanical-Advantage/AdvantageKit/blob/a86d21b27034a36d051798e3eaef167076cd302b/template_projects/sources/vision/src/main/java/frc/robot/Robot.java#L90) and [here](https://github.com/Mechanical-Advantage/AdvantageKit/blob/a86d21b27034a36d051798e3eaef167076cd302b/template_projects/sources/vision/src/main/java/frc/robot/Robot.java#L100) (**IMPORTANT:** You must uncomment _both_ lines). This may improve the consistency of loop cycle timing in some cases, but should be used with caution as it will prevent other threads from running during the user code loop cycle (including internal threads required by NetworkTables, vendors, etc).\n\nThis customization **should only be used if the loop cycle time is significantly less than 20ms**, which allows other threads to continue running between user code cycles. We always recommend **thoroughly testing this change** to ensure that it does not cause unintended side effects (examples include NetworkTables lag, CAN timeouts, etc). In general, **this customization is only recommended for advanced users** who understand the potential side-effects.\n", + "content_preview": "---\nsidebar_position: 5\n---\n\n# Vision Template\n\nThe vision template project provides a starting point for creating a high-performance vision or pose estimation system compatible with AdvantageKit's deterministic log replay." + }, + { + "url": "https://docs.advantagekit.org/getting-started/traditional-replay", + "title": "โช How To: Traditional Replay", + "section": "Getting Started", + "language": "Java", + "content": "---\nsidebar_position: 4\n---\n\n# โช How To: Traditional Replay\n\n## Setup\n\nThe AdvantageKit template projects are preconfigured to support replay by changing the `simMode` option in `Constants.java` to `REPLAY`. More broadly, replay requires the following elements in the logger configuration:\n\n- A log file to use as the source, containing the original inputs and outputs:\n\n```java\n// The log path can be read from anything, but this method is provided for convenience\nString logPath = LogFileUtil.findReplayLog();\n\n// The following sources are used automatically, with these priorities:\n//\n// 1. The value of the \"AKIT_LOG_PATH\" environment variable, if set\n// 2. The file currently open in AdvantageScope, if available\n// 3. The result of the prompt displayed to the user\n```\n\n- A replay source such as `WPILOGReader`:\n\n```java\nLogger.setReplaySource(new WPILOGReader(logPath));\n```\n\n- A data receiver such as `WPILOGWriter`, which will write a new log file containing the new outputs along with the original inputs and outputs:\n\n```java\n// The addPathSuffix function generates a new filename by adding the suffix.\n// If running replay repeatedly, a numeric index is added to the filename instead.\nLogger.addDataReceiver(new WPILOGWriter(LogFileUtil.addPathSuffix(logPath, \"_sim\")));\n```\n\n- Optionally, the robot program can be configured to run faster than real-time. This allows log replay to complete faster than the duration of the original log file and **does not affect the accuracy of log replay**.\n\n```java\nsetUseTiming(false);\n```\n\n## Usage\n\nTo launch log replay, start the robot project in [simulation](https://docs.wpilib.org/en/stable/docs/software/wpilib-tools/robot-simulation/introduction.html). The generated log file will be opened automatically in AdvantageScope (check the API documentation for `WPILOGWriter` for details on customizing this behavior). Replay outputs are stored in the `ReplayOutputs` table alongside the unmodified inputs and outputs (stored in the `RealOutputs` table).\n\n:::tip\nThe simulation GUI **must be disabled** when running in replay. The GUI is disabled by default in the AdvantageKit template projects.\n:::\n\n## Replay Bubble\n\nThe most straightforward uses of replay involve [logging additional outputs](./what-is-advantagekit/example-output-logging.md). Code can also be modified when running in log replay. However, this use case comes with limitations as **modified outputs cannot affect replayed inputs**. This issue is discussed in more detail in the clip below, which is part of 6328's [2025 Championship Conference](./what-is-advantagekit/champs-conference.md).\n\n\n\n![Replay bubble](./img/replay-bubble.png)\n", + "content_preview": "---\nsidebar_position: 4\n---\n\n# โช How To: Traditional Replay\n\n## Setup\n\nThe AdvantageKit template projects are preconfigured to support replay by changing the `simMode` option in `Constants.java` to `REPLAY`." + }, + { + "url": "https://docs.advantagekit.org/getting-started/replay-watch", + "title": "๐Ÿ”ญ How To: Replay Watch", + "section": "Getting Started", + "language": "All", + "content": "---\nsidebar_position: 5\n---\n\n# ๐Ÿ”ญ How To: Replay Watch\n\nSome use cases of log replay benefit from rapid iteration, such as tuning pose estimation algorithms. **Replay watch** addresses this use case by automatically updating replayed outputs when the code is modified. An example is shown below, where the replayed output in AdvantageScope updates in real-time as the code is modified:\n\n\n\n:::info\nCheck the replay example on [rapid iteration](./what-is-advantagekit/example-rapid-iteration.md) for a more detailed example of this feature in the context of tuning a pose estimation algorithm.\n:::\n\n## Usage\n\nReplay watch requires a custom Gradle task defined in `build.gradle`. This task is included in the AdvantageKit template projects and documented in the installation instructions for [existing projects](./installation/existing-projects.md).\n\n1. Configure the project for log replay as normal, such as setting `simMode` in `Constants.java` to `REPLAY` in the AdvantageKit template projects. The `WPILOGWriter` used during replay should be configured using the default, `AUTO`, or `ALWAYS` AdvantageScope open behavior (check the [API docs](pathname:///javadoc/org/littletonrobotics/junction/wpilog/WPILOGWriter.AdvantageScopeOpenBehavior.html) for details). We also recommend calling `setUseTiming(false)` during setup, as described [here](./traditional-replay.md#setup).\n\n2. Open the original log file in AdvantageScope or add the path to the `AKIT_LOG_PATH` environment variable.\n\n3. Run `./gradlew replayWatch` (macOS/Linux) or `gradle.bat replayWatch` (Windows) from the command line.\n\n4. Log replay will run once, and the resulting log file will open automatically in AdvantageScope.\n\n5. When the contents of the `src` directory are modified, log replay will run again automatically and the new log will be opened in AdvantageScope. The time range and visualizations in AdvantageScope will be preserved when loading new data. Note that each iteration of log replay will _overwrite_ the previous replayed log file, but will not modify the original log.\n\n:::tip\nReplay watch is limited by the speed at which the robot program can be replayed, which makes it most useful on **short log files** and devices with **fast single-core CPU performance**.\n:::\n", + "content_preview": "---\nsidebar_position: 5\n---\n\n# ๐Ÿ”ญ How To: Replay Watch\n\nSome use cases of log replay benefit from rapid iteration, such as tuning pose estimation algorithms. **Replay watch** addresses this use case by automatically updating replayed outputs when the code is modified." + }, + { + "url": "https://docs.advantagekit.org/getting-started/common-issues", + "title": "โš ๏ธ Common Issues", + "section": "Common Issues", + "language": "Java", + "content": "Non-Deterministic Data Sources Multithreading Uninitialized Inputs", + "content_preview": "Non-Deterministic Data Sources Multithreading Uninitialized Inputs" + }, + { + "url": "https://docs.advantagekit.org/getting-started/common-issues/multithreading", + "title": "Multithreading", + "section": "Common Issues", + "language": "All", + "content": "---\nsidebar_position: 2\n---\n\n# Multithreading\n\nThe main robot code logic must be single-threaded to work with log replay. This is because the timing of extra threads cannot be recreated in simulation; threads will not execute at the same rate consistently, especially on different hardware.\n\nThere are two solutions to this issue:\n\n- Threads are rarely required in FRC code, so start by considering alternatives. Control loop can often run great at 50Hz in the main robot thread, or consider using the closed-loop features of your preferred motor controller instead.\n- If a thread is truly required, it must be isolated to an IO implementation. Since the inputs to the rest of the robot code are logged periodically, long-running tasks or high-frequency control loops are possible (but as part of an IO implementation, they cannot be recreated during log replay).\n\n## Logging From Threads\n\nAdvantageKit's logging APIs (i.e. `recordOutput` and `processInputs`) are **not** thread-safe, and should only be called from the main thread. As all values in AdvantageKit are synchronized to the main loop cycle, logging data from other threads would fail to capture values accurately even if this functionality was supported. Instead, threads should only be used in IO implementations (see above) and should record all values as inputs, synchronized appropriately to the main loop cycle in a thread-safe manner.\n", + "content_preview": "---\nsidebar_position: 2\n---\n\n# Multithreading\n\nThe main robot code logic must be single-threaded to work with log replay. This is because the timing of extra threads cannot be recreated in simulation; threads will not execute at the same rate consistently, especially on different hardware.\n\nThere..." + }, + { + "url": "https://docs.advantagekit.org/getting-started/common-issues/non-deterministic-data-sources", + "title": "Non-Deterministic Data Sources", + "section": "Common Issues", + "language": "All", + "content": "---\nsidebar_position: 1\n---\n\n# Non-Deterministic Data Sources\n\nAdvantageKit replay relies on all data sources being deterministic and synchronized. IO interfaces ensure this is the case for subsystems, and AdvantageKit automatically handles replay for some core WPILib classes (see [here](/data-flow/built-in-logging) for details). However, it's easy to accidentally use data from sources that are not properly logged. **We recommend regularly testing out log replay during development to confirm that the replay outputs match the real outputs.** Spotting mistakes like this early is the key to fixing them before they become a critical issue at an event.\n\nSome common non-deterministic data sources to watch out for include:\n\n- Timestamp or Driver Station data used before initializing AdvantageKit. See [here](uninitialized-inputs#timestamps--driver-station).\n- Use of raw FPGA timestamps, such as `Timer.getFPGATimestamp()`. Use `Timer.getTimestamp()` instead.\n- NetworkTables data as inputs, including from driver dashboards. See [here](/data-flow/recording-inputs/dashboard-inputs).\n- Large hardware libraries like [YAGSL](https://github.com/BroncBotz3481/YAGSL) or [Phoenix 6 swerve](https://v6.docs.ctr-electronics.com/en/latest/docs/tuner/tuner-swerve/index.html), which interact with hardware directly instead of through an IO layer. Try using the AdvantageKit [swerve template project](/getting-started/template-projects) instead.\n- Interactions with the RIO filesystem. Files can be saved and read by the robot code, but incoming data still needs to be treated as an input.\n- Random number generation, which cannot be recreated in a simulator.\n- Iteration over unordered collections (such as unordered maps).\n", + "content_preview": "---\nsidebar_position: 1\n---\n\n# Non-Deterministic Data Sources\n\nAdvantageKit replay relies on all data sources being deterministic and synchronized. IO interfaces ensure this is the case for subsystems, and AdvantageKit automatically handles replay for some core WPILib classes (see..." + }, + { + "url": "https://docs.advantagekit.org/getting-started/common-issues/uninitialized-inputs", + "title": "Uninitialized Inputs", + "section": "Common Issues", + "language": "Java", + "content": "---\nsidebar_position: 3\n---\n\n# Uninitialized Inputs\n\n## Timestamps & Driver Station\n\nBefore calling `Logger.start()`, AdvantageKit's built-in input logging is not active. This means that Driver Station data (e.g. from the `DriverStation` or joystick classes) and timestamp data (e.g. `Timer.getTimestamp()`) are **not deterministic** and **should not be accessed** by the robot code.\n\n:::tip\nWait to create subsystems or button bindings until after `Logger.start()` is called. For most projects, this can be achieved by instantiating `RobotContainer` at the end of the `Robot` constructor.\n:::\n\n```java\npublic class Robot extends LoggedRobot {\n // DANGER: The Intake constructor runs before Logger.start(),\n // so it can access non-deterministic timestamps and DS data.\n private final Intake intake = new Intake();\n\n // Better: The Flywheel constructor is not called until after\n // Logger.start() has been called in the Robot constructor.\n private final Flywheel flywheel;\n\n public Robot() {\n // ... (AdvantageKit configuration)\n Logger.start();\n\n // Since Logger.start() has been called, the Flywheel\n // constructor is free to use timestamps and DS data.\n flywheel = new Flywheel();\n }\n}\n```\n\n## Subsystems\n\nTypically, inputs from subsystems are only updated during calls to `periodic`. Note that this means updated (non-default) input data is not available in the constructor. The solution is to either wait for the first `periodic` call or call `periodic` from within the constructor.\n\n```java\npublic class Example extends SubsystemBase {\n private final ExampleIO io;\n private final ExampleIOInputs inputs = new ExampleIOInputs();\n\n public Example(ExampleIO io) {\n this.io = io;\n\n // Inputs are not updated yet\n inputs.position;\n }\n\n @Override\n public void periodic() {\n io.updateInputs(inputs);\n Logger.processInputs(\"Example\", inputs);\n\n // Inputs are now updated\n inputs.position;\n }\n}\n```\n", + "content_preview": "---\nsidebar_position: 3\n---\n\n# Uninitialized Inputs\n\n## Timestamps & Driver Station\n\nBefore calling `Logger.start()`, AdvantageKit's built-in input logging is not active. This means that Driver Station data (e.g. from the `DriverStation` or joystick classes) and timestamp data (e.g." + }, + { + "url": "https://docs.advantagekit.org/data-flow/supported-types", + "title": "๐Ÿ“Š Supported Types", + "section": "Data Flow", + "language": "Java", + "content": "---\nsidebar_position: 1\n---\n\n# ๐Ÿ“Š Supported Types\n\nData is stored using string keys where slashes are used to denote subtables (similar to NetworkTables). Like NetworkTables, **all logged values are persistent (they will continue to appear on subsequent cycles until updated**).\n\n### Simple\n\nThe following simple data types are currently supported:\n\n- Single values: `boolean, int, long, float, double, String`\n- Arrays: `boolean[], int[], long[], float[], double[], String[], byte[]`\n- 2D Arrays: `boolean[][], int[][], long[][], float[][], double[][], String[][], byte[][]`\n\n### Structured\n\nMany WPILib classes can be serialized to binary data using [structs](https://github.com/wpilibsuite/allwpilib/blob/main/wpiutil/doc/struct.adoc) or [protobufs](https://protobuf.dev). Supported classes include `Translation2d`, `Pose3d`, and `SwerveModuleState` with more coming soon. These classes can be logged as single values, arrays, or 2D arrays just like any simple type, and used as input or output fields.\n\n:::danger\nProtobuf logging can take an extended period (>100ms) the first time that a value with any given type is logged. Subsequent logging calls using an object of the same type will be significantly faster. **Protobuf values should always be logged for the first time when the robot is disabled.**\n\n_This issue is not applicable to struct logging, which is the default for all data types._\n:::\n\n### Records\n\nCustom [record](https://www.baeldung.com/java-record-keyword) classes can be logged as structs, including support for single values, arrays, and 2D arrays as inputs or outputs. This enables efficient logging of custom complex data types, such as pose observations (check the [vision template](/getting-started/template-projects/vision-template) for examples).\n\nNote that record fields must use only the following struct-compatible types. Array types are not supported for record fields. We recommend logging using multiple top-level record arrays as needed.\n\n- Primitives: `boolean`, `short`, `int`, `long`, `float`, `double`\n- Enum values\n- Struct-compatible types (`Pose2d`, `SwerveModuleState`, etc.)\n- Record values (i.e. nested records)\n\n:::tip\nLogging multiple record types of the same name can cause conflicts. All record classes should be uniquely named.\n:::\n\n:::danger\nRecord logging can take an extended period (>100ms) the first time that a value with any given type is logged. Subsequent logging calls using an object of the same type will be significantly faster. **Record values should always be logged for the first time when the robot is disabled.**\n:::\n\n### Units\n\nAdvantageKit includes extensive support for unit-safe logging, including compatibility with AdvantageScope's [unit visualization](https://docs.advantagescope.org/tab-reference/line-graph/units) and the WPILib [units library](https://docs.wpilib.org/en/latest/docs/software/basic-programming/java-units.html). See the sections below for more information:\n\n- Input logging ([link](/data-flow/recording-inputs/annotation-logging#units))\n- Output logging ([link](/data-flow/recording-outputs/#units))\n- Output annotation logging ([link](/data-flow/recording-outputs/annotation-logging#unit))\n\n### Colors\n\nWPILib includes a [color library](https://github.wpilib.org/allwpilib/docs/release/java/edu/wpi/first/wpilibj/util/Color.html) that can be used to simplify color operations. These values will be stored in the log as a string formatted in [Hex Triplet](https://en.wikipedia.org/wiki/Web_colors) color notation.\n\n### Enums\n\n[Enum](https://www.w3schools.com/java/java_enums.asp) values can be logged and replayed by AdvantageKit. These values will be stored in the log as string values (using the [`name()`](https://docs.oracle.com/javase/8/docs/api/java/lang/Enum.html#name--) method).\n\n### Suppliers (Output Only)\n\nPrimitive suppliers (`BooleanSupplier`, `IntSupplier`, `LongSupplier`, and `DoubleSupplier`) can be used in place of their single values for output logging, including annotation logging with `@AutoLogOutput`. One application of this feature is logging [`Trigger`](https://github.wpilib.org/allwpilib/docs/release/java/edu/wpi/first/wpilibj2/command/button/Trigger.html) values, which extend from `BooleanSupplier`.\n\n### Mechanisms (Output Only)\n\nAdvantageKit can log 2D mechanism objects as outputs, which can be viewed using AdvantageScope. If not using `@AutoLogOutput`, note that the logging call only records the current state of the `Mechanism2d` and so it must be called periodically.\n\n:::tip\nThe `generate3dMechanism()` method can be used to convert a `Mechanism2d` to an array of `Pose3d` objects compatible with [articulated components](https://docs.advantagescope.org/tab-reference/3d-field/#3d-components) in AdvantageScope.\n:::\n\n:::warning\nMechanism objects must use the **`LoggedMechanism2d`** class to be compatible with AdvantageKit. This class is otherwise equivalent to the standard `Mechanism2d` class. Equivalent `LoggedMechanismRoot2d`, `LoggedMechanismObject2d`, and `LoggedMechanismLigament2d` classes are also provided.\n:::\n\n```java\npublic class Example {\n @AutoLogOutput // Auto logged as \"Example/Mechanism\"\n private LoggedMechanism2d mechanism = new LoggedMechanism2d(3, 3);\n\n public void periodic() {\n // Alternative approach if not using @AutoLogOutput\n // (Must be called periodically)\n Logger.recordOutput(\"Example/Mechanism\", mechanism);\n\n // Log Pose3d objects for articulated components in AdvantageScope\n Logger.recordOutput(\"Example/MechanismPoses\", mechanism.generate3dMechanisms());\n }\n}\n```\n", + "content_preview": "---\nsidebar_position: 1\n---\n\n# ๐Ÿ“Š Supported Types\n\nData is stored using string keys where slashes are used to denote subtables (similar to NetworkTables)." + }, + { + "url": "https://docs.advantagekit.org/data-flow/built-in-logging", + "title": "๐Ÿ—’๏ธ Built-In Logging", + "section": "Data Flow", + "language": "Java", + "content": "---\nsidebar_position: 2\n---\n\n# ๐Ÿ—’๏ธ Built-In Logging\n\nAdvantageKit automatically logs many important fields as inputs or outputs. No configuration is required to use these features.\n\n:::info\nBuilt-in input values are available during replay with guaranteed accuracy and can be freely accessed in user code without manual logging. **All other inputs must be logged using an [IO interface](./recording-inputs/io-interfaces.md).**\n:::\n\n## Inputs\n\n### Timestamp\n\nAdvantageKit logs and replays the value of `RobotController.getTime()`, `Timer.getTimestamp()`, etc. See [this page](/theory/deterministic-timestamps) for more details on timestamps in AdvantageKit.\n\n:::warning\nThe methods `RobotController.getFPGATime()` and `Timer.getFPGATimestamp()` are used for accessing the real (non-deterministic) timestamp, and should only be used within IO implementations or for performance profiling.\n:::\n\n### Driver Station\n\nAll values that can be accessed via the `DriverStation` or WPILib HID classes (`Joystick`, `XboxController`, etc) are automatically logged and replayed. These fields are available under the `DriverStation` table.\n\n:::danger\nThe [`waitForDsConnection`]() method is not compatible with AdvantageKit.\n:::\n\n### Dashboard Inputs\n\nDashboard inputs accessed **via AdvantageKit dashboard classes** are automatically logged and replayed. See [this page](./recording-inputs/dashboard-inputs.md) for details. These fields are available under the `NetworkInputs` table.\n\n## Outputs\n\n### Alerts\n\nThe state of any alerts created through WPILib's [persistent alerts](https://docs.wpilib.org/en/latest/docs/software/telemetry/persistent-alerts.html) API will be automatically logged as outputs. These alerts can be visualized using AdvantageScope's ๐Ÿ“‰ [Line Graph](https://docs.advantagescope.org/tab-reference/line-graph) tab. These fields are available under the `RealOutputs` or `ReplayOutputs` table.\n\n![Alerts screenshot](img/alerts-1.png)\n\n### Console\n\nConsole output is automatically logged by AdvantageKit to the `Console` field, and can be viewed using AdvantageScope's ๐Ÿ’ฌ [Console](https://docs.advantagescope.org/tab-reference/console) tab. This field is available under the `RealOutputs` or `ReplayOutputs` table.\n\n:::info\nOutput from native code is not included when running in simulation.\n:::\n\n![Console screenshot](img/console-1.png)\n\n### Radio Status\n\nStatus data from the VH-109 radio is automatically logged every ~5 seconds. This includes useful information about the connection status, bandwidth usage, etc. These fields are available under the `RadioStatus` table.\n\n![Radio data](img/radio-1.png)\n\n### Power Distribution Data\n\nThe current on each channel, along with other useful stats, are automatically logged by AdvantageKit as outputs under the `PowerDistribution` table. This feature works by default when using the CTRE PDP or REV PDH configured to the default CAN ID (ID 0 for the PDP and ID 1 for the PDH).\n\n:::warning\nThis feature is only supported on Power Distribution devices that support current monitoring (the CTRE PDP and REV PDH). The CTRE PDP 2.0 and AndyMark Power Distribution Board are **not supported**.\n:::\n\nThe `LoggedPowerDistribution` class can be used to manually configure the power distribution type and CAN ID when not using the default configuration. This can be accomplished by adding the line below to the `Robot` constructor before `Logger.start()`:\n\n```java\nLoggedPowerDistribution.getInstance(50, ModuleType.kRev); // Example: PDH on CAN ID 50\n```\n\n### System Stats\n\nImportant status information from the roboRIO is automatically recorded, such as the battery voltage, rail status, CAN status, system time, and NT client connections. These fields are available under the `SystemStats` table.\n\n### Performance Data\n\nSeveral important fields are automatically recorded to measure the performance of the robot code:\n\n- `LoggedRobot/FullCycleMS`: The execution time of all periodic code, should be less than the loop period (20ms by default).\n- `LoggedRobot/UserCodeMs`: The execution time of all user periodic code.\n- `LoggedRobot/LogPeriodicMS`: The execution time of all AdvantageKit periodic code.\n- `LoggedRobot/GCTimeMS`: The total execution time of the Java garbage collector within the last loop cycle, may or may not overlap with other code execution.\n- `LoggedRobot/GCCount`: The total number of collections performed by the Java garbage collector within the last loop cycle.\n- `Logger/QueuedCycle`: The number of cycles of data in queue to be written to data receivers.\n- `Logger/...MS`: The execution time of each step of the AdvantageKit periodic code.\n", + "content_preview": "---\nsidebar_position: 2\n---\n\n# ๐Ÿ—’๏ธ Built-In Logging\n\nAdvantageKit automatically logs many important fields as inputs or outputs. No configuration is required to use these features.\n\n:::info\nBuilt-in input values are available during replay with guaranteed accuracy and can be freely accessed in user..." + }, + { + "url": "https://docs.advantagekit.org/data-flow/recording-inputs/io-interfaces", + "title": "IO Interfaces", + "section": "Recording Inputs", + "language": "Java", + "content": "---\nsidebar_position: 1\n---\n\n# IO Interfaces\n\nBy necessity, any interaction with external hardware must be isolated such that all input data is logged and can be replayed in the simulator where that hardware is not present. Most hardware interaction occurs in subsystem classes (read [this section](./dashboard-inputs) for information on using NetworkTables as an input). Traditionally, a subsystem has three main components:\n\n![Diagram of traditional subsystem](img/subsystem-1.png)\n\n- The **public interface** consists of methods used by the rest of the robot code to control the subsystem.\n\n- The **control logic** is the internal code used to follow those commands or analyze sensor data.\n\n- The **hardware interface** is the code used to read sensors and directly control hardware like motors or pneumatics.\n\nData logging of inputs should occur between the control logic and hardware interface - this ensures that any control logic can be replayed in the simulator. We suggest restructuring the subsystem such that hardware interfacing occurs in a separate object (we call this the \"IO\" layer). The IO layer includes an interface defining all methods used for interacting with the hardware along with one or more implementations that make use of vendor libraries to carry out commands and read data.\n\n![Diagram of restructured subsystem](img/subsystem-2.png)\n\n:::tip\nRefer to the [AdvantageKit templates](/getting-started/template-projects) for some reference IO interfaces and implementations.\n:::\n\nOutputs (setting voltage, setpoint, PID constants, etc.) make use of simple methods for each command. Input data is more controlled such that it can be logged and replayed. Each IO interface defines a class with public attributes for all input data, along with methods for saving and replaying that data from a log (`toLog` and `fromLog`). We recommend using the [`@AutoLog`](/data-flow/recording-inputs/annotation-logging) annotation to generate these methods automatically.\n\nThe IO layer includes a single method (`updateInputs`) for updating all of the input data. The subsystem class contains an instance of both the current IO implementation and the \"inputs\" object. Once per cycle, it updates the input data and sends it to the logging framework:\n\n```java\nio.updateInputs(inputs); // Update input data from the IO layer\nLogger.processInputs(\"ExampleSubsystem\", inputs); // Send input data to the logging framework (or update from the log during replay)\n```\n\nThe rest of the subsystem then reads data from this inputs object rather than directly from the IO layer. This structure ensures that:\n\n- The logging framework has access to all of the data being logged and can insert data from the log during replay.\n- Throughout each cycle, all code making use of the input data reads the same values - the cache is never updated _during a cycle_. This means that the data replayed from the log appears identical to the data read on the real robot.\n\nAll of the IO methods include a default implementation which is used during simulation. We suggest setting up each subsystem accept the IO object as a constructor argument, so that the central robot class (like `RobotContainer`) can decide whether or not to use real hardware:\n\n```java\npublic RobotContainer() {\n if (isReal()) {\n // Instantiate IO implementations to talk to real hardware\n driveTrain = new DriveTrain(new DriveTrainIOReal());\n elevator = new Elevator(new ElevatorIOReal());\n intake = new Intake(new IntakeIOReal());\n } else {\n // Use anonymous classes to create \"dummy\" IO implementations\n driveTrain = new DriveTrain(new DriveTrainIO() {});\n elevator = new Elevator(new ElevatorIO() {});\n intake = new Intake(new IntakeIO() {});\n }\n}\n```\n\n:::tip\nWe suggest the use of an IO layer to minimize the chance of interacting with hardware that doesn't exist. However, any structure will work where all input data flows through an inputs object implementing `LoggableInputs` and the two methods `fromLog` and `toLog`. Feel free to make use of whatever structure best fits your own requirements.\n:::\n", + "content_preview": "---\nsidebar_position: 1\n---\n\n# IO Interfaces\n\nBy necessity, any interaction with external hardware must be isolated such that all input data is logged and can be replayed in the simulator where that hardware is not present." + }, + { + "url": "https://docs.advantagekit.org/data-flow/recording-inputs/annotation-logging", + "title": "Annotation Logging", + "section": "Recording Inputs", + "language": "Java", + "content": "---\nsidebar_position: 2\n---\n\n# Annotation Logging\n\nBy adding the `@AutoLog` annotation to your inputs class, AdvantageKit will automatically generate implementations of `toLog` and `fromLog` for your inputs. All [data types](../supported-types.md) are supported with the exception of mechanism states. Loggable inputs can also be nested and used as fields.\n\nFor example:\n\n```java\n@AutoLog\npublic class MyInputs {\n public double myNumber = 0.0;\n public Pose2d myPose = new Pose2d();\n public MyEnum myEnum = MyEnum.VALUE;\n}\n```\n\nThis will generate the following class:\n\n```java\nclass MyInputsAutoLogged extends MyInputs implements LoggableInputs {\n public void toLog(LogTable table) {\n table.put(\"MyNumber\", myField);\n table.put(\"MyPose\", myPose);\n table.put(\"MyEnum\", myEnum);\n }\n\n public void fromLog(LogTable table) {\n myNumber = table.get(\"MyNumber\", myNumber);\n myPose = table.get(\"MyPose\", myPose);\n myEnum = table.get(\"MyEnum\", myEnum);\n }\n}\n```\n\nNote that you should use the `AutoLogged` class, rather than your annotated class. The [AdvantageKit template projects](/getting-started/template-projects) are a useful reference for how to use `@AutoLog` in a full project.\n\n## Units\n\nTo ensure that AdvantageScope will correctly [visualize unit data](https://docs.advantagescope.org/tab-reference/line-graph/units), units can be specified by modifying the field name or using a `Measure` object.\n\n:::info\nUnlike when logged as [outputs](/data-flow/recording-outputs/#units), `Measure` values in an inputs class are always logged using the **base unit** and not the user-specified unit (e.g. distances will always be logged in meters). This ensures that unit differences between IO implementations are not reflected in the logged data.\n:::\n\n```java\n@AutoLog\npublic class MyInputs {\n public double myDistanceMeters = 0.0;\n public Distance myDistance = Meters.of(0.0);\n}\n```\n", + "content_preview": "---\nsidebar_position: 2\n---\n\n# Annotation Logging\n\nBy adding the `@AutoLog` annotation to your inputs class, AdvantageKit will automatically generate implementations of `toLog` and `fromLog` for your inputs." + }, + { + "url": "https://docs.advantagekit.org/data-flow/recording-inputs/dashboard-inputs", + "title": "Dashboard Inputs", + "section": "Recording Inputs", + "language": "Java", + "content": "---\nsidebar_position: 3\n---\n\n# Dashboard Inputs\n\nLike the robot's hardware, **data retrieved from NetworkTables must be isolated and treated as input data.** For example, the following call will NOT function correctly in replay:\n\n```java\nvar flywheelSetpoint = SmartDashboard.getNumber(\"FlywheelSpeed\", 0.0);\n```\n\nAdvantageKit provides several solutions to deal with this issue:\n\n- For subsystems that use NT input data (reading from coprocessors), we recommend treating the NetworkTables interaction as a hardware interface using an IO layer. See the [vision template project](/getting-started/template-projects/vision-template) as an example.\n- When reading dashboard inputs from NT (auto selector, tuning values, etc) AdvantageKit includes the following classes that correctly handle periodic logging and replay:\n - [`LoggedDashboardChooser`](https://github.com/Mechanical-Advantage/AdvantageKit/blob/main/akit/src/main/java/org/littletonrobotics/junction/networktables/LoggedDashboardChooser.java) - Replaces `SendableChooser` with equivalent functionality. See the example below.\n - [`LoggedNetworkNumber`](https://github.com/Mechanical-Advantage/AdvantageKit/blob/main/akit/src/main/java/org/littletonrobotics/junction/networktables/LoggedNetworkNumber.java) - Simple number field\n - [`LoggedNetworkString`](https://github.com/Mechanical-Advantage/AdvantageKit/blob/main/akit/src/main/java/org/littletonrobotics/junction/networktables/LoggedNetworkString.java) - Simple string field\n - [`LoggedNetworkBoolean`](https://github.com/Mechanical-Advantage/AdvantageKit/blob/main/akit/src/main/java/org/littletonrobotics/junction/networktables/LoggedNetworkBoolean.java) - Simple boolean field\n\nExample use of `LoggedDashboardChooser` for auto routines in a command-based project:\n\n```java\nprivate final LoggedDashboardChooser autoChooser = new LoggedDashboardChooser<>(\"Auto Routine\");\n\npublic RobotContainer() {\n // ...\n autoChooser.addDefaultOption(\"Do Nothing\", new InstantCommand());\n autoChooser.addOption(\"My First Auto\", new MyFirstAuto());\n autoChooser.addOption(\"My Second Auto\", new MySecondAuto());\n autoChooser.addOption(\"My Third Auto\", new MyThirdAuto());\n}\n\npublic Command getAutonomousCommand() {\n return autoChooser.get();\n}\n```\n\n:::tip\nAdvantageScope supports tuning via NetworkTables when running in the AdvantageKit NetworkTables mode. Tunable values must be published to the \"/Tuning\" table using `LoggedNetworkNumber`, `LoggedNetworkString`, or `LoggedNetworkBoolean`. Check the [AdvantageScope docs](https://docs.advantagescope.org/overview/live-sources/tuning-mode#tuning-with-advantagekit) for details.\n:::\n\nA `LoggedDashboardChooser` can also be constructed using an existing `SendableChooser`, which allows for compatibility with PathPlanner's `AutoBuilder` API:\n\n```java\nprivate final LoggedDashboardChooser autoChooser;\n\npublic RobotContainer() {\n // ...\n\n // buildAutoChooser() returns a SendableChooser\n autoChooser = new LoggedDashboardChooser<>(\"Auto Routine\", AutoBuilder.buildAutoChooser());\n}\n```\n", + "content_preview": "---\nsidebar_position: 3\n---\n\n# Dashboard Inputs\n\nLike the robot's hardware, **data retrieved from NetworkTables must be isolated and treated as input data.** For example, the following call will NOT function correctly in replay:\n\n```java\nvar flywheelSetpoint =..." + }, + { + "url": "https://docs.advantagekit.org/data-flow/recording-outputs/", + "title": "๐Ÿ”ผ Recording Outputs", + "section": "Recording Outputs", + "language": "Java", + "content": "# ๐Ÿ”ผ Recording Outputs\n\nOutput data consists of any calculated values which could be recreated in the simulator, including...\n\n- Odometry pose\n- Motor voltages\n- Pneumatics commands\n- Status data for drivers\n- Internal object state\n\nThe logging framework supports recording this output data on the real robot and during replay. Essential data like the odometry pose are recorded on the real robot for convenience; even if it can be recreated in a simulator, that's often not a viable option in the rush to fix a problem between matches. During replay, recording extra output data is the primary method of debugging the code - logging calls can be added anywhere as they don't interfere with the replayed control logic. Any loggable data type ([see here](/data-flow/supported-types)) can be saved as an output like so:\n\n```java\nLogger.recordOutput(\"Flywheel/Setpoint\", setpointSpeed);\nLogger.recordOutput(\"Drive/Pose\", odometryPose);\nLogger.recordOutput(\"FeederState\", FeederState.RUNNING);\n```\n\n:::info\nThis data is automatically saved to the `RealOutputs` or `ReplayOutputs` table, and it can be divided further into subtables using slashes (as seen above).\n:::\n\n## Structured Types\n\nLogging geometry objects like `Pose2d`, `Trajectory`, etc. is common in robot code. Many WPILib classes can be serialized to binary data using [structs](https://github.com/wpilibsuite/allwpilib/blob/main/wpiutil/doc/struct.adoc) or [protobufs](https://protobuf.dev). These objects can be logged as single values or arrays:\n\n```java\n// Pose2d\nPose2d poseA, poseB, poseC;\nLogger.recordOutput(\"MyPose2d\", poseA);\nLogger.recordOutput(\"MyPose2dArray\", poseA, poseB);\nLogger.recordOutput(\"MyPose2dArray\", new Pose2d[] { poseA, poseB });\n\n// Pose3d\nPose3d poseA, poseB, poseC;\nLogger.recordOutput(\"MyPose3d\", poseA);\nLogger.recordOutput(\"MyPose3dArray\", poseA, poseB);\nLogger.recordOutput(\"MyPose3dArray\", new Pose3d[] { poseA, poseB });\n\n// Trajectory\nTrajectory trajectory;\nLogger.recordOutput(\"MyTrajectory\", trajectory);\n\n// SwerveModuleState\nSwerveModuleState stateA, stateB, stateC, stateD;\nLogger.recordOutput(\"MySwerveModuleStates\", stateA, stateB, stateC, stateD);\nLogger.recordOutput(\"MySwerveModuleStates\", new SwerveModuleState[] { stateA, stateB, stateC, stateD });\n```\n\n## Units\n\nDouble or float fields can be logged with unit metadata in multiple ways. This ensures that AdvantageScope will correctly [visualize unit data](https://docs.advantagescope.org/tab-reference/line-graph/units).\n\n```java\n// The unit can be specified as a string (stored as metadata)\nLogger.recordOutput(\"MyDistance\", 3.14, \"meters\");\n\n// A unit object from the WPILib units library can be used in place of a string\nLogger.recordOutput(\"MyDistance\", 3.14, Meters);\n\n// Measure values will also be saved with unit metadata\n// (The raw value will use the user-specified unit, not the base unit)\nLogger.recordOutput(\"MyDistance\", Meters.of(3.14));\n\n// This works too, but requires adding the unit to the field name\n// (See the AdvantageScope docs linked above for details)\nLogger.recordOutput(\"MyDistanceMeters\", 3.14);\n```\n", + "content_preview": "# ๐Ÿ”ผ Recording Outputs\n\nOutput data consists of any calculated values which could be recreated in the simulator, including...\n\n- Odometry pose\n- Motor voltages\n- Pneumatics commands\n- Status data for drivers\n- Internal object state\n\nThe logging framework supports recording this output data on the..." + }, + { + "url": "https://docs.advantagekit.org/data-flow/recording-outputs/annotation-logging", + "title": "Annotation Logging", + "section": "Recording Outputs", + "language": "Java", + "content": "---\nsidebar_position: 1\n---\n\n# Annotation Logging\n\nThe `@AutoLogOutput` annotation can also be used to automatically log the value of a field or getter method as an output periodically (including private fields and methods). The key will be selected automatically, or it can be overridden using the `key` parameter. All data types are supported, including arrays and structured data types.\n\n```java\npublic class Example {\n @AutoLogOutput // Logged as \"Example/MyPose\"\n private Pose2d myPose = new Pose2d();\n\n @AutoLogOutput(key = \"Custom/Speeds\")\n public double[] getSpeeds() {...}\n}\n```\n\n## Parameters\n\n### Key\n\nThe `key` parameter can reference other fields within the same class using the syntax shown below. This is useful to disambiguate classes with multiple instances, such as swerve modules. The value of the referenced field will not be updated after the first loop cycle. Any data type convertible to a string is supported, including numbers, booleans, and strings.\n\n```java\npublic class SwerveModule {\n private final int index; // 0, 1, 2, or 3\n private final String descriptor; // \"FL\", \"FR\", \"BL\", \"BR\"\n\n @AutoLogOutput(key = \"Module{index}/Speed\") // e.g. \"Module0/Speed\"\n public double getSpeed() {...}\n\n @AutoLogOutput(key = \"Odometry/ModulePose{descriptor}\") // e.g. \"Odometry/ModulePoseFL\"\n public Pose2d getPose() {...}\n}\n```\n\n### Unit\n\nFor double or float fields, the `unit` parameter can be used to provide a unit name that will be stored as metadata. This ensures that AdvantageScope will correctly [visualize unit data](https://docs.advantagescope.org/tab-reference/line-graph/units). Note that `Measure` values can also be logged using `@AutoLogOutput` with full support for unit metadata.\n\n```java\npublic class Example {\n // AdvantageScope will plot this value using meters\n @AutoLogOutput(unit = \"meters\")\n private double myDistance = 0.0;\n\n // This line is equivalent, if using the WPILib units library\n // (The raw value will use the user-specified unit, not the base unit)\n @AutoLogOutput\n private Distance myDistanceMeasure = Meters.of(0.0);\n\n // This works too, but requires adding the unit to the field name\n // (See the AdvantageScope docs linked above for details)\n @AutoLogOutput\n private double myDistanceMeters = 0.0;\n}\n```\n\n### Force Serializable\n\nThe `forceSerializable` parameter can be used to force the use of struct or Protobuf serialization for enum types, as shown in the example below. The class should inherit from [`StructSerializable`](https://github.wpilib.org/allwpilib/docs/release/java/edu/wpi/first/util/struct/StructSerializable.html), [`ProtobufSerializable`](https://github.wpilib.org/allwpilib/docs/release/java/edu/wpi/first/util/protobuf/ProtobufSerializable.html), or [`WPISerializable`](https://github.wpilib.org/allwpilib/docs/release/java/edu/wpi/first/util/WPISerializable.html).\n\n```java\nenum CustomEnum implements StructSerializable {\n A(...),\n B(...);\n\n public final double value;\n public static final Struct struct = ...;\n\n Setpoint(double value) {\n this.value = value;\n }\n}\n\n@AutoLogOutput\nCustomEnum setpoint = CustomEnum.A; // Logs as an enum\n\n@AutoLogOutput(forceSerializable = true)\nCustomEnum setpoint = CustomEnum.A; // Logs as a struct\n```\n\n## Global Configuration\n\nBy default, the parent class where `@AutoLogOutput` is used must be within the same package as `Robot` (or a subpackage). The following method can be called in the constructor of `Robot` to allow additional packages, such as a \"lib\" package outside of normal robot code:\n\n```java\nAutoLogOutputManager.addPackage(\"frc.lib\");\n```\n\nThe `addObject` method can also be used to manually scan an object for loggable fields. This method should only be called during initialization:\n\n```java\nAutoLogOutputManager.addObject(this);\n```\n\n:::warning\nThe parent class where `@AutoLogOutput` is used must also be instantiated within the first loop cycle and be accessible by a recursive search of the fields of `Robot`. This feature is primarily intended to log outputs from subsystems and other similar classes. For classes that do not fit the criteria above, call `Logger.recordOutput` periodically to record outputs.\n:::\n", + "content_preview": "---\nsidebar_position: 1\n---\n\n# Annotation Logging\n\nThe `@AutoLogOutput` annotation can also be used to automatically log the value of a field or getter method as an output periodically (including private fields and methods)." + }, + { + "url": "https://docs.advantagekit.org/data-flow/sysid-compatibility", + "title": "โš™๏ธ SysId Compatibility", + "section": "Data Flow", + "language": "Java", + "content": "---\nsidebar_position: 5\n---\n\n# โš™๏ธ SysId Compatibility\n\nWPILib provides tools to perform [system identification](https://docs.wpilib.org/en/stable/docs/software/advanced-controls/system-identification/index.html) on robot mechanisms, enabling [feedforward and feedback](https://docs.wpilib.org/en/stable/docs/software/advanced-controls/controllers/index.html) controller gains to be calculated based on real-world data. Starting in 2024, identification routines are defined in user code as described [here](https://docs.wpilib.org/en/stable/docs/software/advanced-controls/system-identification/creating-routine.html). Data is recorded to a WPILOG file for analysis in the SysId application.\n\nSince AdvantageKit already requires subsystems to log relevant sensor data, setting up identification routines in user code is simplified considerably. This document outlines how the process of collecting SysId data differs when using AdvantageKit for data logging. **Please refer to the [WPILib SysId documentation](https://docs.wpilib.org/en/stable/docs/software/advanced-controls/system-identification/index.html) for more details.**\n\n:::tip\nDevice logging systems like AdvantageScope's [URCL](https://docs.advantagescope.org/more-features/urcl) (Unofficial REV-Compatible Logger) and CTRE's [signal logger](https://pro.docs.ctr-electronics.com/en/latest/docs/api-reference/api-usage/signal-logging.html) can be used to collect data instead of AdvantageKit. In this case, please follow the instructions in the corresponding documentation.\n:::\n\n## Code Setup\n\nCreate the `SysIdRoutine` based on the template shown below. Note that the test state is logged as an output through AdvantageKit and the log consumer is set to `null`. This configuration can be performed within the subsystem class.\n\n```java\n// Create the SysId routine\nvar sysIdRoutine = new SysIdRoutine(\n new SysIdRoutine.Config(\n null, null, null, // Use default config\n (state) -> Logger.recordOutput(\"SysIdTestState\", state.toString())\n ),\n new SysIdRoutine.Mechanism(\n (voltage) -> subsystem.runVolts(voltage.in(Volts)),\n null, // No log consumer, since data is recorded by AdvantageKit\n subsystem\n )\n);\n\n// The methods below return Command objects\nsysIdRoutine.quasistatic(SysIdRoutine.Direction.kForward);\nsysIdRoutine.quasistatic(SysIdRoutine.Direction.kReverse);\nsysIdRoutine.dynamic(SysIdRoutine.Direction.kForward);\nsysIdRoutine.dynamic(SysIdRoutine.Direction.kReverse);\n```\n\nRun the SysId routines as normal (described [here](https://docs.wpilib.org/en/stable/docs/software/advanced-controls/system-identification/running-routine.html)), then download the AdvantageKit log file from the robot.\n\n## Loading Data\n\n:::warning\nAdvantageKit log files **should NOT be used directly with SysId**. Follow the instructions below to convert it to the correct format.\n:::\n\nAdvantageKit synchronizes all log updates to the robot loop cycle to enable replay. However, only _changes_ to each field are recorded directly to the log file; by saving the timestamps of each loop cycle, the full set of timestamps where a field was originally recorded can be recreated during replay. This design was chosen because it significantly reduces file size, but it is not compatible with WPILib's SysId analyzer (where explicit updates are expected for every sample, regardless of whether the value changed).\n\nTo convert the AdvantageKit log file to a SysId-compatible format, follow the instructions below:\n\n1. Open the AdvantageKit log file in AdvantageScope v3.0.2 or later. In the menu bar, go to \"File\" > \"Export Data...\".\n\n2. Set the format to \"WPILOG\" and the timestamps to \"AdvantageKit Cycles\". For large log files, enter the prefixes for only the fields and tables necessary for SysId analysis (see the [export options](https://docs.advantagescope.org/more-features/export#options) documentation for details).\n\n3. Click the save icon and choose a location to save the log.\n\n4. Open the SysId analyzer by searching for \"WPILib: Start Tool\" in the VSCode command palette and choosing \"SysId\" (or using the desktop launcher on Windows). Open the exported log file by clicking \"Open data log file...\"\n\n5. Choose the fields to analyze as normal.\n", + "content_preview": "---\nsidebar_position: 5\n---\n\n# โš™๏ธ SysId Compatibility\n\nWPILib provides tools to perform [system identification](https://docs.wpilib.org/en/stable/docs/software/advanced-controls/system-identification/index.html) on robot mechanisms, enabling [feedforward and..." + }, + { + "url": "https://docs.advantagekit.org/theory/log-replay-comparison", + "title": "๐Ÿฆ‹ Log Replay Comparison", + "section": "Theory", + "language": "Java", + "content": "---\nsidebar_position: 1\n---\n\nimport Tabs from '@theme/Tabs';\nimport TabItem from '@theme/TabItem';\n\n# ๐Ÿฆ‹ Log Replay Comparison\n\nFRC teams have access to multiple logging tools that feature \"replay\" capabilities. These fall into the categories of **deterministic replay** ([AdvantageKit](/getting-started/what-is-advantagekit/), [PyKit](https://github.com/1757WestwoodRobotics/PyKit)) and **nondeterministic replay** ([Hoot Replay](https://v6.docs.ctr-electronics.com/en/latest/docs/api-reference/api-usage/hoot-replay.html)). Each type of replay framework offers significantly different capabilities with regard to determinism, playback functionality, and code structure. This page compares these tools to help teams understand their key differences.\n\n:::note\nMany non-replay logging options are also available (such as [WPILib data logging](https://docs.wpilib.org/en/stable/docs/software/telemetry/datalog.html) and [Epilogue](https://docs.wpilib.org/en/stable/docs/software/telemetry/robot-telemetry-with-annotations.html)), but this page focuses exclusively on replay-compatible logging tools.\n:::\n\n## ๐Ÿ”’ Determinism\n\nThe biggest difference between replay frameworks the ability of each tool to replay robot code logic in a way that is **consistent, trustworthy, and robust to timing inconsistency**.\n\n| Deterministic (AdvantageKit, PyKit) | Non-Deterministic (Hoot Replay) |\n| -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| The replayed robot code will always match the behavior of the real robot. The results of replay can be trusted completely to match the actual behavior of the robot. | No guarantees are made about the accuracy of replay in simulation. Data may arrive in replay at different times or at different rates than the real robot, which impacts the accuracy of all parts of the robot code. |\n\nDeterminism has a major impact on the practicality of log replay, since running simulation [faster than real-time](#-rapid-iteration) is a core part of the debugging process in practice. The accuracy of deterministic replay is unaffected by replay speed, while the accuracy of non-deterministic replay decreases when running at faster rates.\n\n### Why Does It Matter?\n\nWe are often asked by teams why they should care about deterministic replay. Non-deterministic replay creates butterfly effects that severely impact the accuracy of replay.\n\n
\n๐Ÿฆ‹ The Butterfly Effect ๐Ÿฆ‹\n\nThe [butterfly effect](https://en.wikipedia.org/wiki/Butterfly_effect) describes how small differences in the inputs to a complex system (like robot code) have ripple effects that can significantly impact the system's behavior in the future. Minor differences in inputs can have a much larger effect on outputs than one might intuitively expect.\n\nThe sequence below provides a simple example of how non-deterministic inputs can impact important parts of the robot code:\n\n1. A vision measurement from a camera is lost or delayed due to non-deterministic replay.\n2. When combined with odometry data in a pose estimator, the estimated pose of the robot is incorrect for one or more loop cycles.\n3. An auto-align command waits for the robot to be within tolerance before scoring. This is a precise operation where errors of less than a centimeter can have a major impact.\n4. The driver presses a button to score just after the real robot is within tolerance. Since the replayed robot's pose is inaccurate, the auto-score command rejects the button input in replay (even though it was accepted on the real robot).\n5. The superstructure of the robot is now being commanded to a different state on the real robot and in replay, since only the real robot continues the scoring operation.\n6. Setpoints to individual mechanisms are now _drastically_ different between the real robot and replay, and do not match the inputs (e.g. encoders) provided to replay. Any tolerance checking of mechanisms is likely to be nonfunctional for the rest of the replay.\n7. Future control inputs will not be correctly obeyed in replay, since the states of many commands and subsystems no longer match the real robot.\n\nThis scenario may seem specific, but similar divergences are **almost inevitable** when replaying robot code of moderate complexity. The testing in the next section demonstrates what this effect looks like in practice.\n\n
\n\nTo demonstrate the impact of deterministic replay, the graphics below show real log data from Team 6328's 2025 robot. To represent each category of replay, this data is based on AdvantageKit's deterministic replay (running ~50x faster than real-time) and a close approximation of Hoot Replay (running <5x faster than real-time).\n\nFirst, the image below shows a few key fields from the AdvantageKit replay. Outputs from the real robot are in blue ๐Ÿ”ต and outputs from AdvantageKit replay are in yellow ๐ŸŸก. The line graph shows the commanded setpoint of the elevator. From bottom to top, the discrete fields show the enabled state, superstructure state, and whether the robot is in tolerance for scoring. Every field displayed here is an **exact match between real and replay**, providing complete trust in the accuracy of the data.\n\n![AdvantageKit Replay](./img/comparison-1.png)\n\nBy contrast, the image below shows the same fields with a close approximation of Hoot Replay running 5x faster than real-time. This is still about 10x _slower_ than AdvantageKit and largely impractical for real debugging workflows. The example shown here is also a _best case scenario_ which includes extensive modifications to the code that compensate for the difference in replay speed.\n\nOutputs from the real robot are in blue ๐Ÿ”ต and outputs from an approximation of Hoot Replay are in green ๐ŸŸข. Within a _few seconds_ of starting the autonomous routine, the state of the robot has **completely diverged between real and replay** due to the butterfly effect. This significantly reduces the value of the log data for debugging, as it no longer resembles the original behavior.\n\n![Hoot Replay (5x, modified)](./img/comparison-2.png)\n\nKeep in mind that replayed outputs are most useful when the equivalent values were not recorded by the real robot (i.e. there is no reference point to verify accuracy). For that critical use case, there is _no way to distinguish accurate outputs_ from the inaccurate, diverged outputs shown above. This undermines the core purpose of replay, as the outputs cannot be trusted for debugging.\n\n
\nMore Details\n\nThe graphs above shows the results of replaying 5x faster than real-time with additional modifications to compensate for loop cycle time, though these changes would not be part of a typical robot project. We have provided several other test cases to demonstrate the impact of different replay settings:\n\n**5x faster than real-time, typical robot project:**\n\n![Hoot Replay (5x, unmodified)](./img/comparison-3.png)\n\n**2x faster than real-time, typical robot project:**\n\n![Hoot Replay (2x, unmodified)](./img/comparison-4.png)\n\n**2x faster than real-time, compensated:**\n\n![Hoot Replay (2x, modified)](./img/comparison-5.png)\n\nNote that even the _very best case_ shown in the last graph still breaks down completely midway through the match, and is unable to replay critical fields like the auto scoring tolerance.\n\n**What about other fields?**\n\nIt is true that some fields are more affected by replay inaccuracy than others. For example, the graph below compares the X position of drive odometry between the real robot and Hoot Replay running 5x faster than real-time. Odometry is only affected by the drive motors, so it is less subject to the butterfly effect than other parts of the code (though it still drifts several feet by the end of the match).\n\nLog replay is most helpful when untangling complex code logic that is nontrivial to recreate without the full set of input data, as demonstrated even in our [simplest examples](/getting-started/what-is-advantagekit/example-output-logging). Odometry data and other trivial fields serve as a partial exception to the butterfly effect, but (as noted above) the lack of reference points when running replay in practice means that it is never possible to distinguish non-deterministic outputs that are _slightly inaccurate_ (odometry) from the majority of outputs that are _completely inaccurate_.\n\n![Odometry: Hoot Replay (5x, modified)](./img/comparison-6.png)\n\n**What about skipping in time?**\n\nThe [section below](#-rapid-iteration) explains why rapid iteration and running faster than real-time are critical to any replay workflow, which is why the examples above demonstrate the impact of running Hoot Replay faster than real-time. However, one could also start the replay at a later point in the log file to work around the slow speed of non-deterministic replay.\n\nThe graph below demonstrates why this approach is ineffective, by skipping to the middle of teleop before running simulated Hoot Replay (2x faster than real-time with loop cycle compensation). Even in this best-case scenario for Hoot Replay running at only 2x speed, the replay is completely unable to match the real outputs. Skipping large parts of the log massively increases the impact of the butterfly effect by completely changing the set of inputs accessible to the replayed code. One should not expect to see accurate outputs at any speed unless all of the inputs are accounted for during replay.\n\n![Skipping: Hoot Replay (2x, modified)](./img/comparison-7.png)\n\n
\n\n## ๐Ÿ’จ Rapid Iteration\n\nLog replay can be used in a variety of environments, which take advantage of the ability to rapidly iterate on code or debug issues without access to the robot. Here are a few examples where replay can play a critical role in the debugging process:\n\n- Debugging complex logic issues between matches without access to a practice field.\n- Retuning an auto-score tolerance in the pits based on data from the last match.\n- Testing a variety of vision filtering techniques between in-person meetings.\n- Remotely debugging issues for a team by repeatedly logging additional outputs.\n- Generating outputs after every match that are too complex to run on the RoboRIO.\n\nEvery one of these use cases **depends on being able to run replay faster than real-time**. A typical match log may be 10 minutes long, and a replay feature that takes 10 minutes to run is not practical in any of these scenarios. Whether log replay is used under time pressure at an event or at home for rapid debugging, quickly running multiple replays with different outputs or tuning parameters is absolutely core to its utility.\n\n### Comparison\n\n| AdvantageKit/PyKit | Hoot Replay |\n| ------------------------------------------------------------------- | -------------------------------------------------- |\n| โœ… Run as fast as possible (e.g. ~50x real-time) | โŒ Accuracy decreases with faster speeds |\n| โœ… [Replay Watch](/getting-started/replay-watch) for fast iteration | โŒ Replay process is fully manual |\n| โœ… Pull and push logs directly to AdvantageScope | โŒ Manual file management, multiple logs per match |\n\nDeterministic replay means that accuracy is unaffected by the replay speed. Running replay ~50 times faster than real-time is common, which means that **a 10 minute match log can be replayed in just _12 seconds_**. AdvantageKit is designed to make rapid iteration as painless as possible through features like [Replay Watch](/getting-started/replay-watch) and integration with AdvantageScope; just open a log, run replay, and see the results with _no manual log management required_.\n\nBy contrast, Hoot Replay's non-deterministic approach presents users with difficult trade-offs between accuracy and practicality. Running at just 5x speed already has a **[major impact](#why-does-it-matter) on accuracy while still taking a full _2 minutes_** per replay iteration. Non-determinism makes replay more difficult to use in the high-pressure scenarios where it matters the most.\n\nThe video below demonstrates what the difference in speed between deterministic and non-deterministic replay looks like in practice on a short 5:48 match log. Several replays of the same log are synchronized and shown in real-time.\n\n\n\n## ๐Ÿงฑ Code Structure\n\nWhile Hoot Replay involves significant trade-offs, its core design goal is to \"simplify\" hardware interactions. Unlike AdvantageKit, some subsystems may be compatible with Hoot Replay while using CTRE's standard subsystem structure (combining high-level logic, hardware configuration, low-level controls, and simulation in a single class).\n\nSubsystems under Hoot Replay fall into the two categories shown below. Note that users must select a **single CAN bus** to replay, which means that many subsystems using entirely CTRE devices are not natively compatible with Hoot Replay. For subsystems that are not natively compatible, **every input must be manually logged and replayed**.\n\n| **Natively Compatible** | **Manual Logging** |\n| ------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n|
  • CTRE devices on the replayed CAN bus
|
  • All other CTRE devices
  • Non-CTRE devices
  • Non-CAN sensors (e.g. RIO data)
  • Network devices (e.g. Limelight, PhotonVision)
  • Dashboard inputs (e.g. auto choosers)
|\n\n### Hardware Abstraction vs. Data Injection\n\nAll replay frameworks sometimes require users to use alternative structures that maintain compatibility with replay. AdvantageKit and PyKit build all subsystems around [hardware abstraction](/data-flow/recording-inputs/io-interfaces), which provides a clean separation between parts of the code logic that must be isolated: high-level logic, simulation, and replayed code are never able to interact in unintended ways.\n\nThe table below compares the implications of this structure against Hoot Replay's approach:\n\n| | [Hardware Abstraction](/data-flow/recording-inputs/io-interfaces)
(AdvantageKit, PyKit) | [Data Injection](https://v6.docs.ctr-electronics.com/en/docs-2026-beta/docs/api-reference/api-usage/hoot-replay.html#adjusting-robot-code-architecture)
(Hoot Replay) |\n| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| **Code Structure** | The functions of each subsystem are divided into several smaller classes. | All functions of the subsystem are combined into a single large class. |\n| **Templates** | โœ… AdvantageKit provides [template projects](/getting-started/template-projects) for many subsystems including swerve drives and vision systems (compatible with several vendors). | โš ๏ธ Minimal examples are provided. No template projects for subsystems with manual logging. |\n| **Data Flow** | โœ… Data flow is well-defined to ensure clean separation between real, replay, and sim modes. | โŒ All data is accessible to all parts of the subsystem. Careful planning and frequent testing is required to ensure that modes are well-separated. |\n| **Input Logging** | โœ… Error-free logging of a large number of inputs is facilitated by [annotation](/data-flow/recording-inputs/annotation-logging) and [record](/data-flow/supported-types#records) logging. | โŒ Each new input field requires several lines of additional boilerplate, which can easily cause subtle issues during replay if implemented incorrectly. |\n| **Dashboards** | โœ… Convenience classes are provided to simplify the process of using [dashboard inputs](/data-flow/recording-inputs/dashboard-inputs). | โŒ All data must be logged manually by the user, even outside of subsystems. |\n\n### Example: Vision Subsystem\n\nThe code below represents a feature-complete Limelight vision subsystem built with both AdvantageKit (hardware abstraction) and Hoot Replay (data injection):\n\n- The AdvantageKit version creates clean separation between the different components of the vision system, making each class easier to understand and debug. The hardware interface with automatic logging enforces clear and correct data flows _by default_. Annotation, record, and enum logging also allow complex data types to be logged with minimal effort, as shown in the `VisionIOInputs` class below.\n- The Hoot Replay version combines all of the functionality in a single class, with manual hooks to read and write data for each input field. Note that there is no obvious separation between the the replayed and non-replayed parts of the code, making it easy to read from invalid data sources or replay data incorrectly. The minimal utilities for logging complex types also result in a confusing structure for input data.\n\n
\n\n\n\n\n\n\n_Vision subsystem (103 lines)_\n\n```java\npublic class Vision extends SubsystemBase {\n private final VisionConsumer consumer;\n private final VisionIO io;\n private final VisionIOInputsAutoLogged inputs = new VisionIOInputsAutoLogged();\n private final Alert disconnectedAlert =\n new Alert(\"Vision camera is disconnected.\", AlertType.kWarning);\n\n public Vision(VisionConsumer consumer, VisionIO io) {\n this.consumer = consumer;\n this.io = io;\n }\n\n /** Returns the X angle to the best target, which can be used for simple servoing with vision. */\n public Rotation2d getTargetX() {\n return inputs.latestTargetObservation.tx();\n }\n\n @Override\n public void periodic() {\n io.updateInputs(inputs);\n Logger.processInputs(\"Vision\", inputs);\n\n // Update disconnected alert\n disconnectedAlert.set(!inputs.connected);\n\n // Initialize logging values\n List tagPoses = new LinkedList<>();\n List robotPoses = new LinkedList<>();\n List robotPosesAccepted = new LinkedList<>();\n List robotPosesRejected = new LinkedList<>();\n\n // Add tag poses\n for (int tagId : inputs.tagIds) {\n var tagPose = aprilTagLayout.getTagPose(tagId);\n if (tagPose.isPresent()) {\n tagPoses.add(tagPose.get());\n }\n }\n\n // Loop over pose observations\n for (var observation : inputs.poseObservations) {\n // Check whether to reject pose\n boolean rejectPose =\n observation.tagCount() == 0 // Must have at least one tag\n || (observation.tagCount() == 1\n && observation.ambiguity() > maxAmbiguity) // Cannot be high ambiguity\n || Math.abs(observation.pose().getZ()) > maxZError // Must have realistic Z coordinate\n\n // Must be within the field boundaries\n || observation.pose().getX() < 0.0\n || observation.pose().getX() > aprilTagLayout.getFieldLength()\n || observation.pose().getY() < 0.0\n || observation.pose().getY() > aprilTagLayout.getFieldWidth();\n\n // Add pose to log\n robotPoses.add(observation.pose());\n if (rejectPose) {\n robotPosesRejected.add(observation.pose());\n } else {\n robotPosesAccepted.add(observation.pose());\n }\n\n // Skip if rejected\n if (rejectPose) {\n continue;\n }\n\n // Calculate standard deviations\n double stdDevFactor =\n Math.pow(observation.averageTagDistance(), 2.0) / observation.tagCount();\n double linearStdDev = linearStdDevBaseline * stdDevFactor;\n double angularStdDev = angularStdDevBaseline * stdDevFactor;\n if (observation.type() == PoseObservationType.MEGATAG_2) {\n linearStdDev *= linearStdDevMegatag2Factor;\n angularStdDev *= angularStdDevMegatag2Factor;\n }\n\n // Send vision observation\n consumer.accept(\n observation.pose().toPose2d(),\n observation.timestamp(),\n VecBuilder.fill(linearStdDev, linearStdDev, angularStdDev));\n }\n\n // Log camera metadata\n Logger.recordOutput(\"Vision/TagPoses\", tagPoses.toArray(new Pose3d[0]));\n Logger.recordOutput(\"Vision/RobotPoses\", robotPoses.toArray(new Pose3d[0]));\n Logger.recordOutput(\"Vision/RobotPosesAccepted\", robotPosesAccepted.toArray(new Pose3d[0]));\n Logger.recordOutput(\"Vision/RobotPosesRejected\", robotPosesRejected.toArray(new Pose3d[0]));\n }\n\n @FunctionalInterface\n public static interface VisionConsumer {\n public void accept(\n Pose2d visionRobotPoseMeters,\n double timestampSeconds,\n Matrix visionMeasurementStdDevs);\n }\n}\n```\n\n\n\n\n_Vision hardware interface (30 lines)_\n\n```java\npublic interface VisionIO {\n @AutoLog\n public static class VisionIOInputs {\n public boolean connected = false;\n public TargetObservation latestTargetObservation =\n new TargetObservation(Rotation2d.kZero, Rotation2d.kZero);\n public PoseObservation[] poseObservations = new PoseObservation[0];\n public int[] tagIds = new int[0];\n }\n\n /** Represents the angle to a simple target, not used for pose estimation. */\n public static record TargetObservation(Rotation2d tx, Rotation2d ty) {}\n\n /** Represents a robot pose sample used for pose estimation. */\n public static record PoseObservation(\n double timestamp,\n Pose3d pose,\n double ambiguity,\n int tagCount,\n double averageTagDistance,\n PoseObservationType type) {}\n\n public static enum PoseObservationType {\n MEGATAG_1,\n MEGATAG_2,\n PHOTONVISION\n }\n\n public default void updateInputs(VisionIOInputs inputs) {}\n}\n```\n\n\n\n\n_Vision hardware implementation (103 lines)_\n\n```java\npublic class VisionIOLimelight implements VisionIO {\n private final Supplier rotationSupplier;\n private final DoubleArrayPublisher orientationPublisher;\n\n private final DoubleSubscriber latencySubscriber;\n private final DoubleSubscriber txSubscriber;\n private final DoubleSubscriber tySubscriber;\n private final DoubleArraySubscriber megatag1Subscriber;\n private final DoubleArraySubscriber megatag2Subscriber;\n\n /**\n * Creates a new VisionIOLimelight.\n *\n * @param name The configured name of the Limelight.\n * @param rotationSupplier Supplier for the current estimated rotation, used for MegaTag 2.\n */\n public VisionIOLimelight(String name, Supplier rotationSupplier) {\n var table = NetworkTableInstance.getDefault().getTable(name);\n this.rotationSupplier = rotationSupplier;\n orientationPublisher = table.getDoubleArrayTopic(\"robot_orientation_set\").publish();\n latencySubscriber = table.getDoubleTopic(\"tl\").subscribe(0.0);\n txSubscriber = table.getDoubleTopic(\"tx\").subscribe(0.0);\n tySubscriber = table.getDoubleTopic(\"ty\").subscribe(0.0);\n megatag1Subscriber = table.getDoubleArrayTopic(\"botpose_wpiblue\").subscribe(new double[] {});\n megatag2Subscriber =\n table.getDoubleArrayTopic(\"botpose_orb_wpiblue\").subscribe(new double[] {});\n }\n\n @Override\n public void updateInputs(VisionIOInputs inputs) {\n // Update connection status based on whether an update has been seen in the last 250ms\n inputs.connected =\n ((RobotController.getFPGATime() - latencySubscriber.getLastChange()) / 1000) < 250;\n\n // Update target observation\n inputs.latestTargetObservation =\n new TargetObservation(\n Rotation2d.fromDegrees(txSubscriber.get()), Rotation2d.fromDegrees(tySubscriber.get()));\n\n // Update orientation for MegaTag 2\n orientationPublisher.accept(\n new double[] {rotationSupplier.get().getDegrees(), 0.0, 0.0, 0.0, 0.0, 0.0});\n NetworkTableInstance.getDefault()\n .flush(); // Increases network traffic but recommended by Limelight\n\n // Read new pose observations from NetworkTables\n Set tagIds = new HashSet<>();\n List poseObservations = new LinkedList<>();\n for (var rawSample : megatag1Subscriber.readQueue()) {\n if (rawSample.value.length == 0) continue;\n for (int i = 11; i < rawSample.value.length; i += 7) {\n tagIds.add((int) rawSample.value[i]);\n }\n poseObservations.add(\n new PoseObservation(\n rawSample.timestamp * 1.0e-6 - rawSample.value[6] * 1.0e-3,\n parsePose(rawSample.value),\n rawSample.value.length >= 18 ? rawSample.value[17] : 0.0,\n (int) rawSample.value[7],\n rawSample.value[9],\n PoseObservationType.MEGATAG_1));\n }\n for (var rawSample : megatag2Subscriber.readQueue()) {\n if (rawSample.value.length == 0) continue;\n for (int i = 11; i < rawSample.value.length; i += 7) {\n tagIds.add((int) rawSample.value[i]);\n }\n poseObservations.add(\n new PoseObservation(\n rawSample.timestamp * 1.0e-6 - rawSample.value[6] * 1.0e-3,\n parsePose(rawSample.value),\n 0.0,\n (int) rawSample.value[7],\n rawSample.value[9],\n PoseObservationType.MEGATAG_2));\n }\n\n // Save pose observations to inputs object\n inputs.poseObservations = new PoseObservation[poseObservations.size()];\n for (int i = 0; i < poseObservations.size(); i++) {\n inputs.poseObservations[i] = poseObservations.get(i);\n }\n\n // Save tag IDs to inputs objects\n inputs.tagIds = new int[tagIds.size()];\n int i = 0;\n for (int id : tagIds) {\n inputs.tagIds[i++] = id;\n }\n }\n\n /** Parses the 3D pose from a Limelight botpose array. */\n private static Pose3d parsePose(double[] rawLLArray) {\n return new Pose3d(\n rawLLArray[0],\n rawLLArray[1],\n rawLLArray[2],\n new Rotation3d(\n Units.degreesToRadians(rawLLArray[3]),\n Units.degreesToRadians(rawLLArray[4]),\n Units.degreesToRadians(rawLLArray[5])));\n }\n}\n```\n\n\n\n\n\n\n_Vision subsystem and hardware interface (248 lines)_\n\n```java\npublic class HootVision extends SubsystemBase {\n private final VisionConsumer consumer;\n private final Alert disconnectedAlert =\n new Alert(\"Vision camera is disconnected.\", AlertType.kWarning);\n private final Supplier rotationSupplier;\n private final DoubleArrayPublisher orientationPublisher;\n\n private final DoubleSubscriber latencySubscriber;\n private final DoubleSubscriber txSubscriber;\n private final DoubleSubscriber tySubscriber;\n private final DoubleArraySubscriber megatag1Subscriber;\n private final DoubleArraySubscriber megatag2Subscriber;\n\n private boolean connected = false;\n private Rotation2d latestTargetObservationTx = Rotation2d.kZero;\n private Rotation2d latestTargetObservationTy = Rotation2d.kZero;\n private double[] timestamps = new double[] {};\n private Pose3d[] poses = new Pose3d[] {};\n private double[] ambiguities = new double[] {};\n private int[] tagCounts = new int[] {};\n private double[] averageTagDistances = new double[] {};\n private int[] types = new int[] {};\n public int[] tagIds = new int[] {};\n\n private final HootAutoReplay hootReplay =\n new HootAutoReplay()\n .withBoolean(\"Connected\", () -> connected, (value) -> connected = value.value)\n .withStruct(\n \"Vision/LatestTargetObservationTx\",\n Rotation2d.struct,\n () -> latestTargetObservationTx,\n (value) -> latestTargetObservationTx = value.value)\n .withStruct(\n \"Vision/LatestTargetObservationTy\",\n Rotation2d.struct,\n () -> latestTargetObservationTy,\n (value) -> latestTargetObservationTx = value.value)\n .withDoubleArray(\n \"Vision/Timestamps\", () -> timestamps, (value) -> timestamps = value.value)\n .withStructArray(\n \"Vision/Poses\", Pose3d.struct, () -> poses, (value) -> poses = value.value)\n .withDoubleArray(\n \"Vision/Ambiguities\", () -> ambiguities, (value) -> ambiguities = value.value)\n .withIntegerArray(\n \"Vision/Timestamps\",\n () -> Arrays.stream(tagCounts).mapToLong(i -> i).toArray(),\n (value) -> tagCounts = Arrays.stream(value.value).mapToInt(i -> (int) i).toArray())\n .withDoubleArray(\n \"Vision/AverageTagDistances\",\n () -> averageTagDistances,\n (value) -> averageTagDistances = value.value)\n .withIntegerArray(\n \"Vision/Types\",\n () -> Arrays.stream(types).mapToLong(i -> i).toArray(),\n (value) -> types = Arrays.stream(value.value).mapToInt(i -> (int) i).toArray())\n .withIntegerArray(\n \"Vision/TagIds\",\n () -> Arrays.stream(tagIds).mapToLong(i -> i).toArray(),\n (value) -> tagIds = Arrays.stream(value.value).mapToInt(i -> (int) i).toArray());\n\n public HootVision(VisionConsumer consumer, String name, Supplier rotationSupplier) {\n this.consumer = consumer;\n var table = NetworkTableInstance.getDefault().getTable(name);\n this.rotationSupplier = rotationSupplier;\n orientationPublisher = table.getDoubleArrayTopic(\"robot_orientation_set\").publish();\n latencySubscriber = table.getDoubleTopic(\"tl\").subscribe(0.0);\n txSubscriber = table.getDoubleTopic(\"tx\").subscribe(0.0);\n tySubscriber = table.getDoubleTopic(\"ty\").subscribe(0.0);\n megatag1Subscriber = table.getDoubleArrayTopic(\"botpose_wpiblue\").subscribe(new double[] {});\n megatag2Subscriber =\n table.getDoubleArrayTopic(\"botpose_orb_wpiblue\").subscribe(new double[] {});\n }\n\n @Override\n public void periodic() {\n if (!Utils.isReplay()) {\n // Update connection status based on whether an update has been seen in the last 250ms\n connected =\n ((RobotController.getFPGATime() - latencySubscriber.getLastChange()) / 1000) < 250;\n\n // Update target observation\n latestTargetObservationTx = Rotation2d.fromDegrees(txSubscriber.get());\n latestTargetObservationTy = Rotation2d.fromDegrees(tySubscriber.get());\n\n // Update orientation for MegaTag 2\n orientationPublisher.accept(\n new double[] {rotationSupplier.get().getDegrees(), 0.0, 0.0, 0.0, 0.0, 0.0});\n NetworkTableInstance.getDefault()\n .flush(); // Increases network traffic but recommended by Limelight\n\n // Read new pose observations from NetworkTables\n Set tagIds = new HashSet<>();\n List poseObservations = new LinkedList<>();\n for (var rawSample : megatag1Subscriber.readQueue()) {\n if (rawSample.value.length == 0) continue;\n for (int i = 11; i < rawSample.value.length; i += 7) {\n tagIds.add((int) rawSample.value[i]);\n }\n poseObservations.add(\n new PoseObservation(\n rawSample.timestamp * 1.0e-6 - rawSample.value[6] * 1.0e-3,\n parsePose(rawSample.value),\n rawSample.value.length >= 18 ? rawSample.value[17] : 0.0,\n (int) rawSample.value[7],\n rawSample.value[9],\n PoseObservationType.MEGATAG_1));\n }\n for (var rawSample : megatag2Subscriber.readQueue()) {\n if (rawSample.value.length == 0) continue;\n for (int i = 11; i < rawSample.value.length; i += 7) {\n tagIds.add((int) rawSample.value[i]);\n }\n poseObservations.add(\n new PoseObservation(\n rawSample.timestamp * 1.0e-6 - rawSample.value[6] * 1.0e-3,\n parsePose(rawSample.value),\n 0.0,\n (int) rawSample.value[7],\n rawSample.value[9],\n PoseObservationType.MEGATAG_2));\n }\n\n // Save pose observations to inputs\n timestamps = new double[poseObservations.size()];\n poses = new Pose3d[poseObservations.size()];\n ambiguities = new double[poseObservations.size()];\n tagCounts = new int[poseObservations.size()];\n averageTagDistances = new double[poseObservations.size()];\n types = new int[poseObservations.size()];\n for (int i = 0; i < poseObservations.size(); i++) {\n var obs = poseObservations.get(i);\n timestamps[i] = obs.timestamp();\n poses[i] = obs.pose();\n ambiguities[i] = obs.ambiguity();\n tagCounts[i] = obs.tagCount();\n averageTagDistances[i] = obs.averageTagDistance();\n types[i] = obs.type().ordinal();\n }\n\n // Save tag IDs to inputs\n this.tagIds = new int[tagIds.size()];\n int i = 0;\n for (int tagId : tagIds) {\n this.tagIds[i++] = tagId;\n }\n }\n hootReplay.update();\n\n // Update disconnected alert\n disconnectedAlert.set(!connected);\n\n // Initialize logging values\n List tagPoses = new LinkedList<>();\n List robotPoses = new LinkedList<>();\n List robotPosesAccepted = new LinkedList<>();\n List robotPosesRejected = new LinkedList<>();\n\n // Add tag poses\n for (int tagId : tagIds) {\n var tagPose = aprilTagLayout.getTagPose(tagId);\n if (tagPose.isPresent()) {\n tagPoses.add(tagPose.get());\n }\n }\n\n // Loop over pose observations\n for (int i = 0; i < timestamps.length; i++) {\n // Check whether to reject pose\n boolean rejectPose =\n tagCounts[i] == 0 // Must have at least one tag\n || (tagCounts[i] == 1 && ambiguities[i] > maxAmbiguity) // Cannot be high ambiguity\n || Math.abs(poses[i].getZ()) > maxZError // Must have realistic Z coordinate\n\n // Must be within the field boundaries\n || poses[i].getX() < 0.0\n || poses[i].getX() > aprilTagLayout.getFieldLength()\n || poses[i].getY() < 0.0\n || poses[i].getY() > aprilTagLayout.getFieldWidth();\n\n // Add pose to log\n robotPoses.add(poses[i]);\n if (rejectPose) {\n robotPosesRejected.add(poses[i]);\n } else {\n robotPosesAccepted.add(poses[i]);\n }\n\n // Skip if rejected\n if (rejectPose) {\n continue;\n }\n\n // Calculate standard deviations\n double stdDevFactor = Math.pow(averageTagDistances[i], 2.0) / tagCounts[i];\n double linearStdDev = linearStdDevBaseline * stdDevFactor;\n double angularStdDev = angularStdDevBaseline * stdDevFactor;\n if (types[i] == 1) {\n linearStdDev *= linearStdDevMegatag2Factor;\n angularStdDev *= angularStdDevMegatag2Factor;\n }\n\n // Send vision observation\n consumer.accept(\n poses[i].toPose2d(),\n timestamps[i],\n VecBuilder.fill(linearStdDev, linearStdDev, angularStdDev));\n }\n\n // Log camera metadata\n SignalLogger.writeStructArray(\n \"Vision/TagPoses\", Pose3d.struct, tagPoses.toArray(new Pose3d[0]));\n SignalLogger.writeStructArray(\n \"Vision/RobotPoses\", Pose3d.struct, robotPoses.toArray(new Pose3d[0]));\n SignalLogger.writeStructArray(\n \"Vision/RobotPosesAccepted\",\n Pose3d.struct,\n robotPosesAccepted.toArray(new Pose3d[0]));\n SignalLogger.writeStructArray(\n \"Vision/RobotPosesRejected\",\n Pose3d.struct,\n robotPosesRejected.toArray(new Pose3d[0]));\n }\n\n /** Returns the X angle to the best target, which can be used for simple servoing with vision. */\n public Rotation2d getTargetX() {\n return Rotation2d.fromDegrees(txSubscriber.get());\n }\n\n /** Parses the 3D pose from a Limelight botpose array. */\n private static Pose3d parsePose(double[] rawLLArray) {\n return new Pose3d(\n rawLLArray[0],\n rawLLArray[1],\n rawLLArray[2],\n new Rotation3d(\n Units.degreesToRadians(rawLLArray[3]),\n Units.degreesToRadians(rawLLArray[4]),\n Units.degreesToRadians(rawLLArray[5])));\n }\n\n @FunctionalInterface\n public static interface VisionConsumer {\n public void accept(\n Pose2d visionRobotPoseMeters,\n double timestampSeconds,\n Matrix visionMeasurementStdDevs);\n }\n}\n```\n\n:::danger\nDid you notice that this example of Hoot Replay actually has **three separate** subtle but critical issues that prevent replay from functioning? The monolithic structure of data injection and a lack of automatic logging options make subtle typos extremely common and challenging to debug.\n:::\n\n\n\n\n
\n\n## ๐Ÿ“‹ Miscellaneous\n\nThe table below provides an overview of the differences between each replay tool. Note that some of the restrictions of Hoot Replay can be addressed via complex manual logging as discussed above.\n\n| | AdvantageKit | PyKit | Hoot Replay |\n| ------------------- | -------------------------------------------------- | ------------------------------------------- | --------------------------------------------------------------------------------------- |\n| **Accuracy** | โœ… Deterministic | โœ… Deterministic | โŒ Non-deterministic |\n| **Rapid Iteration** | โœ… Replay at any speed | โœ… Replay at any speed | โŒ Accuracy decreases with speed |\n| **Code Structure** | โœ… Hardware abstraction + automatic logging | โœ… Hardware abstraction + automatic logging | โŒ Manual data injection |\n| **Vendor** | โœ… No restriction + templates for multiple vendors | โœ… No restriction | โŒ Vendor-locked to CTRE devices |\n| **CAN Buses** | โœ… No restriction | โœ… No restriction | โŒ Requires a single CAN bus |\n| **FRC Languages** | Java | Python | Java, Python, C++ |\n| **Pricing** | Free & Open Source | Free & Open Source | ๐Ÿ’ฐ Subscription: Requires [Phoenix Pro](https://store.ctr-electronics.com/phoenix-pro/) |\n| **Users in 2025** | 598 teams | NA | <10 teams |\n\n:::note\nThe number of AdvantageKit users is based on official usage reporting data published by FIRST. The number of Hoot Replay users is estimated based on a search of public GitHub repositories using Hoot Replay and the percentage of all teams that publish code on GitHub.\n:::\n", + "content_preview": "---\nsidebar_position: 1\n---\n\nimport Tabs from '@theme/Tabs';\nimport TabItem from '@theme/TabItem';\n\n# ๐Ÿฆ‹ Log Replay Comparison\n\nFRC teams have access to multiple logging tools that feature \"replay\" capabilities." + }, + { + "url": "https://docs.advantagekit.org/theory/deterministic-timestamps", + "title": "โฐ Deterministic Timestamps", + "section": "Theory", + "language": "Java", + "content": "---\nsidebar_position: 3\n---\n\n# โฐ Deterministic Timestamps\n\n### The Problem\n\nTo guarantee accurate replay, all of the data used by the robot code must be included in the log file and replayed in simulation. This includes the current timestamp, which may be used for calculations in control loops. WPILib's default behavior is to read the timestamp directly from the FPGA every time a method like `Timer.getTimestamp()` is called. Every return value will be slightly different as the timestamp continues increasing throughout each loop cycle. This behavior is problematic for AdvantageKit replay because the return values from those method calls are not logged and **cannot be accurately replayed in simulation**.\n\nAdvantageKit's solution is to use _synchronized timestamps_. The timestamp is read once from the FPGA at the start of the loop cycle and injected into WPILib. By default, all calls to `Timer.getTimestamp()` or similar return the synchronized timestamp from AdvantageKit instead of the \"real\" FPGA time. During replay, the logged timestamp is used for each loop cycle. The result is that all of the control logic is _deterministic_ and will **exactly match the behavior of the real robot**.\n\n### Solution #1\n\nWhere precise timestamps are required, the best solution is to record measurement timestamps as part of the input data for each subsystem. For example, [NetworkTables](https://docs.wpilib.org/en/stable/docs/software/networktables/publish-and-subscribe.html#subscribing-to-a-topic) and [Phoenix 6](https://api.ctr-electronics.com/phoenix6/release/java/com/ctre/phoenix6/StatusSignal.SignalMeasurement.html#timestamp) include methods to read the timestamp of each update, which can be used in calculations for wheel odometry, vision localization, or other precise controls alongside AdvantageKit's deterministic timestamps. For most use cases, measuring the timestamp of the original sample is more desirable than measuring the precise timestamp on the robot.\n\n### Solution #2\n\n`Timer.getFPGATimestamp()` can always be used to access the \"real\" FPGA timestamp where necessary, like within IO implementations or for analyzing performance. This method is not affected by log replay (i.e. it will not reflect the accelerated rate of replay). One use case for this method is measuring code execution time, since those values don't need to be recreated during log replay. WPILib classes like `Watchdog` use this method because they use the timestamp for analyzing performance and are not part of the robot's control logic.\n\n### Solution #3\n\nOptionally, AdvantageKit allows you to disable deterministic timestamps. This reverts to the default WPILib behavior of reading from the FPGA for every method call, making the behavior of the robot code non-deterministic. The \"output\" values seen in simulation may be slightly different than they were on the real robot. This alternative mode should only be used when _all_ of the following are true:\n\n1. The control logic depends on the exact timestamp _within_ a single loop cycle, like a high precision control loop that is significantly affected by the precise time that it is executed within each (usually 20ms) loop cycle.\n2. The sensor values used in the loop cannot be associated with timestamps in an IO implementation. See solution #1.\n3. The IO (sensors, actuators, etc) involved in the loop are sufficiently low-latency that the exact timestamp on the RIO is significant. For example, CAN motor controllers are limited by the rate of their CAN frames, so the extra precision on the RIO is insignificant in most cases.\n\nIf you need to disable deterministic timestamps globally, add the following lines to the constructor of `Robot` _after_ `Logger.start()`:\n\n```java\nif (!Logger.hasReplaySource()) {\n RobotController.setTimeSource(RobotController::getFPGATime);\n}\n```\n", + "content_preview": "---\nsidebar_position: 3\n---\n\n# โฐ Deterministic Timestamps\n\n### The Problem\n\nTo guarantee accurate replay, all of the data used by the robot code must be included in the log file and replayed in simulation. This includes the current timestamp, which may be used for calculations in control loops." + }, + { + "url": "https://docs.advantagekit.org/theory/high-frequency-odometry", + "title": "๐Ÿ“ High-Frequency Odometry", + "section": "Theory", + "language": "All", + "content": "---\nsidebar_position: 2\n---\n\nimport Results from \"./img/high-freq-odometry-3.webp\";\n\n# ๐Ÿ“ High-Frequency Odometry\n\nThe AdvantageKit swerve templates support high-frequency odometry on both Spark and TalonFX(S) hardware, which means that data from the drive motors, encoders, and gyro are sampled _faster_ than the primary 50 Hz loop cycle. The purpose of high-frequency odometry is to improve the accuracy and consistency of odometry data.\n\nThe testing below was conducted by Team 6328 in November 2023 to measure the benefit of high-frequency odometry compared to traditional 50 Hz odometry. Note that this data is broadly applicable to any application of this technique, not just the AdvantageKit templates. This testing builds on the work done by CTRE [here](https://pro.docs.ctr-electronics.com/en/latest/docs/application-notes/update-frequency-impact.html#practical-results), which was performed under inconsistent testing conditions with a very small sample size.\n\n## Setup\n\nWe focused our testing on the accuracy of odometry during an autonomous path with no vision assistance. This is where the accuracy of wheel odometry is most critical โ€” during teleop, additional sensors like vision will _always_ be necessary to maintain accuracy.\n\nUsing a NEO-based swerve, we ran a ~12 second PathPlanner auto as shown below. This is intended to be representative of the type of movement that might be seen during an auto path, including holonomic rotations, movement in multiple directions, and occasional hard accelerations.\n\n![Test path](./img/high-freq-odometry-1.webp)\n\nThe start location of the auto is marked with the large white cross, and the robot was commanded to return to the same position. Log data shows that the robot's odometry always reached the target with negligible error. Using a camera positioned in the ceiling, we are able to estimate the robotโ€™s true ending position based on a tape marker at the center of the robot (example shown below). Any error from the target position to the robotโ€™s position represents odometry error that was accumulated during the path.\n\n![Example measurement](./img/high-freq-odometry-2.jpeg)\n\n## Results\n\nWe repeated this auto 16 times โ€” 8 times with 50Hz odometry and 8 times with 250Hz odometry. The ending positions are plotted below in meters, where blue is 50Hz and orange in 250Hz. The dark circles represent the average ending position of each set of samples.\n\n\"Results\"\n\nThe mean error and standard deviations are shown below. Note that the improvement in mean error was relatively small compared to the improvement in standard deviation. In practice, this means that **autos are unlikely to be more accurate, but they will be much more consistent/precise.**\n\n| | Mean Error (m) | Standard Deviation (m) |\n| ------------- | -------------- | ---------------------- |\n| 50Hz | 0.388 | 0.180 |\n| 250Hz | 0.297 | 0.028 |\n| % Improvement | 23.3% | 84.4% |\n\n## CANivore Timesync\n\nThe TalonFX(S) swerve template for AdvantageKit supports [CANivore Timesync](https://pro.docs.ctr-electronics.com/en/latest/docs/api-reference/api-usage/status-signals.html#canivore-timesync) for Phoenix Pro subscribers running the drive on a [CANivore](https://pro.docs.ctr-electronics.com/en/latest/docs/canivore/canivore-intro.html). CTRE conducted testing on the impact of timesync on odometry accuracy, which can be found [here](https://pro.docs.ctr-electronics.com/en/latest/docs/application-notes/update-frequency-impact.html#after-test-data). This part of CTRE's testing was based on a more reliable testing methodology than the rest of the linked page, using autonomous driving similar to the approach described here.\n\nThey found an improvement in error of ~15% and in standard deviation of ~34%. This is a measurable but relatively modest improvement compared to high-frequency odometry, and similarly has a more significant impact on _consistency_ (standard deviation) than overall _accuracy_ (error).\n", + "content_preview": "---\nsidebar_position: 2\n---\n\nimport Results from \"./img/high-freq-odometry-3.webp\";\n\n# ๐Ÿ“ High-Frequency Odometry\n\nThe AdvantageKit swerve templates support high-frequency odometry on both Spark and TalonFX(S) hardware, which means that data from the drive motors, encoders, and gyro are sampled..." + }, + { + "url": "https://docs.advantagekit.org/theory/case-studies", + "title": "๐Ÿ’ผ Replay Case Studies", + "section": "Case Studies", + "language": "Java", + "content": "Elevator Profile Autoscoring Command Gremlins Aiming Functions AprilTag Vision Traditional Vision", + "content_preview": "Elevator Profile Autoscoring Command Gremlins Aiming Functions AprilTag Vision Traditional Vision" + }, + { + "url": "https://docs.advantagekit.org/theory/case-studies/aiming-functions", + "title": "Aiming Functions", + "section": "Case Studies", + "language": "All", + "content": "---\nsidebar_position: 4\n---\n\n# Aiming Functions\n\n\n", + "content_preview": "---\nsidebar_position: 4\n---\n\n# Aiming Functions\n\n\n", + "content_preview": "---\nsidebar_position: 5\n---\n\n# AprilTag Vision\n\n\n", + "content_preview": "---\nsidebar_position: 2\n---\n\n# Autoscoring\n\n\n", + "content_preview": "---\nsidebar_position: 3\n---\n\n# Command Gremlins\n\n\n", + "content_preview": "---\nsidebar_position: 1\n---\n\n# Elevator Profile\n\n\n", + "content_preview": "---\nsidebar_position: 6\n---\n\n# Traditional Vision\n\n