From f1c9d82d506637ca786d6c32bf933813522dffe6 Mon Sep 17 00:00:00 2001 From: Thad House Date: Sat, 20 Jun 2026 10:28:24 -0700 Subject: [PATCH] [hal,wpilib] Add DS Display API (#8975) --- .../wpilib/hardware/hal/DriverStationJNI.java | 8 + .../main/native/cpp/FIRSTDriverStation.cpp | 5 + .../main/native/cpp/jni/DriverStationJNI.cpp | 15 ++ hal/src/main/native/cpp/mrclib/MrcLibDs.cpp | 7 + .../native/include/wpi/hal/DriverStation.h | 7 + .../native/include/wpi/hal/cpp/MrcLibDs.hpp | 8 + hal/src/main/native/sim/DriverStation.cpp | 6 + .../main/python/semiwrap/DriverStation_c.yml | 1 + shared/bazel/thirdparty/integrity_helper.py | 2 +- .../thirdparty/mrclib/mrclib.MODULE.bazel | 16 +- shared/examplecheck.gradle | 2 +- shared/libmrclib.gradle | 2 +- wpilibc/robotpy_pybind_build_info.bzl | 10 + .../driverstation/DriverStationDisplay.cpp | 147 ++++++++++++ .../driverstation/DriverStationDisplay.hpp | 86 +++++++ wpilibc/src/main/python/pyproject.toml | 1 + .../python/semiwrap/DriverStationDisplay.yml | 11 + wpilibc/src/main/python/wpilib/__init__.py | 2 + wpilibcExamples/example_projects.bzl | 2 + .../DriverStationDisplayAnsi/cpp/Robot.cpp | 18 ++ .../cpp/opmode/DefaultTeleop.cpp | 47 ++++ .../include/Robot.hpp | 12 + .../include/opmode/DefaultTeleop.hpp | 20 ++ .../DriverStationDisplayLines/cpp/Robot.cpp | 18 ++ .../cpp/opmode/DefaultTeleop.cpp | 29 +++ .../include/Robot.hpp | 12 + .../include/opmode/DefaultTeleop.hpp | 18 ++ .../src/main/cpp/examples/examples.json | 22 ++ .../driverstation/DriverStationDisplay.java | 227 ++++++++++++++++++ wpilibjExamples/example_projects.bzl | 2 + .../DefaultTeleop.java | 53 ++++ .../driverstationdisplayansi/Robot.java | 13 + .../DefaultTeleop.java | 34 +++ .../driverstationdisplaylines/Robot.java | 13 + .../java/org/wpilib/examples/examples.json | 24 ++ 35 files changed, 889 insertions(+), 11 deletions(-) create mode 100644 wpilibc/src/main/native/cpp/driverstation/DriverStationDisplay.cpp create mode 100644 wpilibc/src/main/native/include/wpi/driverstation/DriverStationDisplay.hpp create mode 100644 wpilibc/src/main/python/semiwrap/DriverStationDisplay.yml create mode 100644 wpilibcExamples/src/main/cpp/examples/DriverStationDisplayAnsi/cpp/Robot.cpp create mode 100644 wpilibcExamples/src/main/cpp/examples/DriverStationDisplayAnsi/cpp/opmode/DefaultTeleop.cpp create mode 100644 wpilibcExamples/src/main/cpp/examples/DriverStationDisplayAnsi/include/Robot.hpp create mode 100644 wpilibcExamples/src/main/cpp/examples/DriverStationDisplayAnsi/include/opmode/DefaultTeleop.hpp create mode 100644 wpilibcExamples/src/main/cpp/examples/DriverStationDisplayLines/cpp/Robot.cpp create mode 100644 wpilibcExamples/src/main/cpp/examples/DriverStationDisplayLines/cpp/opmode/DefaultTeleop.cpp create mode 100644 wpilibcExamples/src/main/cpp/examples/DriverStationDisplayLines/include/Robot.hpp create mode 100644 wpilibcExamples/src/main/cpp/examples/DriverStationDisplayLines/include/opmode/DefaultTeleop.hpp create mode 100644 wpilibj/src/main/java/org/wpilib/driverstation/DriverStationDisplay.java create mode 100644 wpilibjExamples/src/main/java/org/wpilib/examples/driverstationdisplayansi/DefaultTeleop.java create mode 100644 wpilibjExamples/src/main/java/org/wpilib/examples/driverstationdisplayansi/Robot.java create mode 100644 wpilibjExamples/src/main/java/org/wpilib/examples/driverstationdisplaylines/DefaultTeleop.java create mode 100644 wpilibjExamples/src/main/java/org/wpilib/examples/driverstationdisplaylines/Robot.java diff --git a/hal/src/main/java/org/wpilib/hardware/hal/DriverStationJNI.java b/hal/src/main/java/org/wpilib/hardware/hal/DriverStationJNI.java index e6ba322878..6d4e07cc0c 100644 --- a/hal/src/main/java/org/wpilib/hardware/hal/DriverStationJNI.java +++ b/hal/src/main/java/org/wpilib/hardware/hal/DriverStationJNI.java @@ -346,6 +346,14 @@ public class DriverStationJNI extends JNIWrapper { */ public static native boolean getOutputsActive(); + /** + * Writes ANSI text to the display. + * + * @param data the ANSI text to write + * @see "HAL_WriteDisplayAnsi" + */ + public static native void writeDisplayAnsi(String data); + /** Utility class. */ private DriverStationJNI() {} } diff --git a/hal/src/main/native/cpp/FIRSTDriverStation.cpp b/hal/src/main/native/cpp/FIRSTDriverStation.cpp index 0c262e67fe..8d33f91131 100644 --- a/hal/src/main/native/cpp/FIRSTDriverStation.cpp +++ b/hal/src/main/native/cpp/FIRSTDriverStation.cpp @@ -3,6 +3,7 @@ // the WPILib BSD license file in the root directory of this project. #include +#include #include #include #include @@ -279,6 +280,10 @@ HAL_Bool HAL_GetSystemTimeValid(int32_t* status) { return systemTimeValid; } +void HAL_WriteDisplayAnsi(const struct WPI_String* data) { + mrcLibDs->writeDisplayAnsi(data); +} + } // extern "C" namespace wpi::hal { diff --git a/hal/src/main/native/cpp/jni/DriverStationJNI.cpp b/hal/src/main/native/cpp/jni/DriverStationJNI.cpp index bb39cd4e2b..56e1bdbfeb 100644 --- a/hal/src/main/native/cpp/jni/DriverStationJNI.cpp +++ b/hal/src/main/native/cpp/jni/DriverStationJNI.cpp @@ -444,4 +444,19 @@ Java_org_wpilib_hardware_hal_DriverStationJNI_getOutputsActive { return HAL_GetOutputsEnabled(); } + +/* + * Class: org_wpilib_hardware_hal_DriverStationJNI + * Method: writeDisplayAnsi + * Signature: (Ljava/lang/String;)V + */ +JNIEXPORT void JNICALL +Java_org_wpilib_hardware_hal_DriverStationJNI_writeDisplayAnsi + (JNIEnv* env, jclass, jstring data) +{ + JStringRef dataStr{env, data}; + WPI_String dataWpiStr = dataStr.wpi_str(); + HAL_WriteDisplayAnsi(&dataWpiStr); +} + } // extern "C" diff --git a/hal/src/main/native/cpp/mrclib/MrcLibDs.cpp b/hal/src/main/native/cpp/mrclib/MrcLibDs.cpp index f0660ca2c9..2ec23c4791 100644 --- a/hal/src/main/native/cpp/mrclib/MrcLibDs.cpp +++ b/hal/src/main/native/cpp/mrclib/MrcLibDs.cpp @@ -242,6 +242,8 @@ class MrcLibDsImpl : public MrcLibDs { int32_t getSystemTimeValid(bool* systemTimeValid) override; + int32_t writeDisplayAnsi(const struct WPI_String* line) override; + wpi::util::EventVector newDataEvents; private: @@ -661,6 +663,11 @@ int32_t MrcLibDsImpl::getSystemTimeValid(bool* systemTimeValid) { return status; } +int32_t MrcLibDsImpl::writeDisplayAnsi(const struct WPI_String* line) { + MRC_String mrcLine = WPIStringToMRCString(line); + return MRC_DsCommsControl_WriteAnsi(&mrcLine); +} + void MrcLibDsImpl::provideNewDataEventHandle(WPI_EventHandle handle) { newDataEvents.Add(handle); } diff --git a/hal/src/main/native/include/wpi/hal/DriverStation.h b/hal/src/main/native/include/wpi/hal/DriverStation.h index 015f8ad63f..479446bae0 100644 --- a/hal/src/main/native/include/wpi/hal/DriverStation.h +++ b/hal/src/main/native/include/wpi/hal/DriverStation.h @@ -315,6 +315,13 @@ void HAL_ObserveUserProgramStarting(void); */ void HAL_ObserveUserProgram(HAL_ControlWord word); +/** + * Writes ANSI text to the display. + * + * @param data the ANSI text to write + */ +void HAL_WriteDisplayAnsi(const struct WPI_String* data); + #ifdef __cplusplus } // extern "C" #endif diff --git a/hal/src/main/native/include/wpi/hal/cpp/MrcLibDs.hpp b/hal/src/main/native/include/wpi/hal/cpp/MrcLibDs.hpp index 1c848ecf7c..bd9015f756 100644 --- a/hal/src/main/native/include/wpi/hal/cpp/MrcLibDs.hpp +++ b/hal/src/main/native/include/wpi/hal/cpp/MrcLibDs.hpp @@ -97,6 +97,14 @@ class MrcLibDs { virtual int32_t observeUserProgram(HAL_ControlWord controlWord) = 0; virtual int32_t getSystemTimeValid(bool* systemTimeValid) = 0; + + /** + * Writes ANSI text to the Driver Station display. + * + * @param line the ANSI text to write + * @return 0 on success, non-zero on error + */ + virtual int32_t writeDisplayAnsi(const struct WPI_String* line) = 0; }; MrcLibDs* GetMrcLibDs(); diff --git a/hal/src/main/native/sim/DriverStation.cpp b/hal/src/main/native/sim/DriverStation.cpp index b1fcab94a2..5b332d8085 100644 --- a/hal/src/main/native/sim/DriverStation.cpp +++ b/hal/src/main/native/sim/DriverStation.cpp @@ -160,6 +160,8 @@ class MrcLibDsSimImpl : public MrcLibDs { int32_t getSystemTimeValid(bool* systemTimeValid) override; + int32_t writeDisplayAnsi(const struct WPI_String* line) override; + wpi::util::EventVector newDataEvents; void NewDriverStationData(); @@ -506,6 +508,10 @@ int32_t MrcLibDsSimImpl::getSystemTimeValid(bool* systemTimeValid) { return 0; } +int32_t MrcLibDsSimImpl::writeDisplayAnsi(const struct WPI_String* line) { + return 0; +} + void MrcLibDsSimImpl::NewDriverStationData() { SimDriverStationData->dsAttached = true; cacheToUpdate->Update(); diff --git a/hal/src/main/python/semiwrap/DriverStation_c.yml b/hal/src/main/python/semiwrap/DriverStation_c.yml index b7928a749b..48f22d5670 100644 --- a/hal/src/main/python/semiwrap/DriverStation_c.yml +++ b/hal/src/main/python/semiwrap/DriverStation_c.yml @@ -45,3 +45,4 @@ functions: HAL_SetOpModeOptions: HAL_ObserveUserProgram: HAL_GetGameData: + HAL_WriteDisplayAnsi: diff --git a/shared/bazel/thirdparty/integrity_helper.py b/shared/bazel/thirdparty/integrity_helper.py index e4759f418c..d7cd571270 100644 --- a/shared/bazel/thirdparty/integrity_helper.py +++ b/shared/bazel/thirdparty/integrity_helper.py @@ -157,7 +157,7 @@ def update_libssh(): def update_mrclib(): # Keep in sync with shared/libmrclib.gradle - version = "2027.1.0-alpha-1-50-gd008523" + version = "2027.1.0-alpha-1-65-g21f308e" has_headers = True classifiers = [ diff --git a/shared/bazel/thirdparty/mrclib/mrclib.MODULE.bazel b/shared/bazel/thirdparty/mrclib/mrclib.MODULE.bazel index 280662ed69..2fee9519a8 100644 --- a/shared/bazel/thirdparty/mrclib/mrclib.MODULE.bazel +++ b/shared/bazel/thirdparty/mrclib/mrclib.MODULE.bazel @@ -2,17 +2,17 @@ http_archive = use_repo_rule("@bazel_tools//tools/build_defs/repo:http.bzl", "ht ################################################################################ # Generated by shared/bazel/thirdparty/integrity_helper.py -url_pattern = "https://frcmaven.wpi.edu/artifactory/development-2027/org/wpilib/mrclib/mrclib-cpp/2027.1.0-alpha-1-50-gd008523/mrclib-cpp-2027.1.0-alpha-1-50-gd008523-%s.zip" +url_pattern = "https://frcmaven.wpi.edu/artifactory/development-2027/org/wpilib/mrclib/mrclib-cpp/2027.1.0-alpha-1-65-g21f308e/mrclib-cpp-2027.1.0-alpha-1-65-g21f308e-%s.zip" -headers_integrity = "c71011e2c593749aca585eec308f394d5e7f34451c1a14cdd8e077ea3b1368b5" +headers_integrity = "a8b38a00d584b50c3bb84800121df1022ac1500f59e4ecac85068adca7c0ac49" _LIB_ARTIFACTS = { - "linuxarm64": ("linux", "**/*.so*", "4673c573c25d1d9a03f1cf3d73c1e8a121718291171213813b95b451eea53f3a"), - "linuxx86-64": ("linux", "**/*.so*", "8d986ed0ffb03be1215a3cf88716c31da82848f4524e723ac89aee4eca45eb12"), - "osxuniversal": ("osx", "**/*.dylib", "af05c46bffa58a1ca63bd18f4d8865b827746d1cdf94c8512c7f86d58731d16f"), - "linuxsystemcore": ("linux", "**/*.so*", "c1cad49fb96caa73fddbc852588c67a2110402539b39dba40255c21974114e84"), - "windowsarm64": ("windows", "**/*.dll", "8205701efbde585c6822981c7246c0a0d8e4b4cdb702097ec77674aa98aff7bb"), - "windowsx86-64": ("windows", "**/*.dll", "dd4eef6e1aaba5b7034889a1646e259b3e91435b3084d43b3ff3b2c6adcc2122"), + "linuxarm64": ("linux", "**/*.so*", "38009110f0deeaeaec08b59fe37ce7e87d3ee4f3d86bd70a7003b22a75bcb43e"), + "linuxx86-64": ("linux", "**/*.so*", "503b3107febd35d32d28e5a3135c48fbd8c09d49d0b1f404182bc9b3c6877d12"), + "osxuniversal": ("osx", "**/*.dylib", "422a52e4b5f859ef3afb7a97e66ba72572894a73abd88126e09647929b1701c1"), + "linuxsystemcore": ("linux", "**/*.so*", "f84d4a6852d52da5535c054944b45f6f0c3d98271756ebfa28103a6f45e073e5"), + "windowsarm64": ("windows", "**/*.dll", "8fd028fa1144c1c18adf9facf4dd690f6d888565c73498bfae01849d0400dad0"), + "windowsx86-64": ("windows", "**/*.dll", "6e905f298fd261e68abb2bfcad7d47334d57f8b6dd14046bf2b98f55c7a657d7"), } # End auto-gen ################################################################################ diff --git a/shared/examplecheck.gradle b/shared/examplecheck.gradle index a071660b76..1e271339eb 100644 --- a/shared/examplecheck.gradle +++ b/shared/examplecheck.gradle @@ -46,7 +46,7 @@ def tagList = [ "Swerve Drive", /* --- Telemetry --- */ - "SmartDashboard", "Shuffleboard", "Sendable", "DataLog", + "SmartDashboard", "Shuffleboard", "Sendable", "DataLog", "Driver Station", /* --- Controls --- */ "Exponential Profile", "PID", "State-Space", "LTVUnicycleController", "Path Following", diff --git a/shared/libmrclib.gradle b/shared/libmrclib.gradle index 571687e501..b3b59a2d3a 100644 --- a/shared/libmrclib.gradle +++ b/shared/libmrclib.gradle @@ -5,7 +5,7 @@ nativeUtils { artifactId = "mrclib-cpp" headerClassifier = "headers" ext = "zip" - version = '2027.1.0-alpha-1-50-gd008523' + version = '2027.1.0-alpha-1-65-g21f308e' skipAtRuntimePlatforms.add(nativeUtils.wpi.platforms.systemcore) targetPlatforms.addAll(nativeUtils.wpi.platforms.allPlatforms) noDebugSplit = true diff --git a/wpilibc/robotpy_pybind_build_info.bzl b/wpilibc/robotpy_pybind_build_info.bzl index 6164e8b22f..9549542acd 100644 --- a/wpilibc/robotpy_pybind_build_info.bzl +++ b/wpilibc/robotpy_pybind_build_info.bzl @@ -145,6 +145,16 @@ def wpilib_extension(srcs = [], header_to_dat_deps = [], extra_hdrs = [], includ ("wpi::DriverStation", "wpi__DriverStation.hpp"), ], ), + struct( + class_name = "DriverStationDisplay", + yml_file = "semiwrap/DriverStationDisplay.yml", + header_root = "$(execpath :robotpy-native-wpilib.copy_headers)", + header_file = "$(execpath :robotpy-native-wpilib.copy_headers)/wpi/driverstation/DriverStationDisplay.hpp", + tmpl_class_names = [], + trampolines = [ + ("wpi::DriverStationDisplay", "wpi__DriverStationDisplay.hpp"), + ], + ), struct( class_name = "MatchState", yml_file = "semiwrap/MatchState.yml", diff --git a/wpilibc/src/main/native/cpp/driverstation/DriverStationDisplay.cpp b/wpilibc/src/main/native/cpp/driverstation/DriverStationDisplay.cpp new file mode 100644 index 0000000000..e865c55ad5 --- /dev/null +++ b/wpilibc/src/main/native/cpp/driverstation/DriverStationDisplay.cpp @@ -0,0 +1,147 @@ +// Copyright (c) FIRST and other WPILib contributors. +// Open Source Software; you can modify and/or share it under the terms of +// the WPILib BSD license file in the root directory of this project. + +#include "wpi/driverstation/DriverStationDisplay.hpp" + +#include +#include +#include +#include +#include + +#include "wpi/hal/DriverStation.h" +#include "wpi/hal/monotonic_clock.hpp" +#include "wpi/util/StringMap.hpp" +#include "wpi/util/mutex.hpp" +#include "wpi/util/string.hpp" + +using namespace wpi; + +static constexpr std::chrono::milliseconds UPDATE_PERIOD{230}; + +static constexpr std::string_view CLEAR_DISPLAY = "\033[0m\033[2J\033[H"; + +namespace { +struct DriverStationDisplayStorage { + wpi::util::mutex mutex; + bool rawMode = false; + wpi::util::StringMap lineMap; + std::vector lines; + std::string buffer; +}; +} // namespace + +static DriverStationDisplayStorage& GetDisplayStorage() { + static DriverStationDisplayStorage storage; + return storage; +} + +static void WriteDisplayAnsiToHal(std::string_view data) { + WPI_String dataWpiStr = wpi::util::make_string(data); + HAL_WriteDisplayAnsi(&dataWpiStr); +} + +void DriverStationDisplay::SetMode(Mode mode) { + auto& storage = GetDisplayStorage(); + std::scoped_lock lock(storage.mutex); + + switch (mode) { + case Mode::Line: + storage.rawMode = false; + storage.lineMap.clear(); + storage.lines.clear(); + break; + case Mode::RawAnsi: + storage.rawMode = true; + WriteDisplayAnsiToHal(CLEAR_DISPLAY); + break; + } +} + +void DriverStationDisplay::AddData(std::string_view caption, + std::string_view line) { + auto& storage = GetDisplayStorage(); + std::scoped_lock lock(storage.mutex); + if (storage.rawMode) { + return; + } + + bool hasCaption = false; + for (char ch : caption) { + if (!std::isspace(static_cast(ch))) { + hasCaption = true; + break; + } + } + if (!hasCaption) { + storage.lines.emplace_back(line); + return; + } + + uint32_t lineNum; + auto it = storage.lineMap.find(caption); + if (it == storage.lineMap.end()) { + lineNum = storage.lines.size(); + storage.lineMap[caption] = lineNum; + storage.lines.emplace_back(line); + } else { + lineNum = it->second; + if (lineNum < storage.lines.size()) { + storage.lines[lineNum] = line; + } + } +} + +void DriverStationDisplay::AddLine(std::string_view line) { + AddData({}, line); +} + +void DriverStationDisplay::UpdateLines() { + auto& storage = GetDisplayStorage(); + std::scoped_lock lock(storage.mutex); + if (storage.rawMode) { + return; + } + + static auto lastDisplayUpdate = + wpi::hal::monotonic_clock::now() - UPDATE_PERIOD; + auto now = wpi::hal::monotonic_clock::now(); + if (now - lastDisplayUpdate < UPDATE_PERIOD) { + storage.lineMap.clear(); + storage.lines.clear(); + return; + } + lastDisplayUpdate = now; + + storage.buffer.clear(); + storage.buffer += CLEAR_DISPLAY; + for (const auto& line : storage.lines) { + storage.buffer += line; + storage.buffer += "\033[0m\n"; + } + + WPI_String wpiBuffer = wpi::util::make_string(storage.buffer); + HAL_WriteDisplayAnsi(&wpiBuffer); + + storage.lineMap.clear(); + storage.lines.clear(); +} + +void DriverStationDisplay::WriteRawAnsi(std::string_view data) { + auto& storage = GetDisplayStorage(); + std::scoped_lock lock(storage.mutex); + if (!storage.rawMode) { + return; + } + + WriteDisplayAnsiToHal(data); +} + +void DriverStationDisplay::ClearRaw() { + auto& storage = GetDisplayStorage(); + std::scoped_lock lock(storage.mutex); + if (storage.rawMode) { + WriteDisplayAnsiToHal(CLEAR_DISPLAY); + } +} diff --git a/wpilibc/src/main/native/include/wpi/driverstation/DriverStationDisplay.hpp b/wpilibc/src/main/native/include/wpi/driverstation/DriverStationDisplay.hpp new file mode 100644 index 0000000000..17739283ac --- /dev/null +++ b/wpilibc/src/main/native/include/wpi/driverstation/DriverStationDisplay.hpp @@ -0,0 +1,86 @@ +// Copyright (c) FIRST and other WPILib contributors. +// Open Source Software; you can modify and/or share it under the terms of +// the WPILib BSD license file in the root directory of this project. + +#pragma once + +#include + +namespace wpi { + +/** + * Provides access to the Driver Station display. + * + * Line mode is the default display mode. + */ +class DriverStationDisplay final { + public: + DriverStationDisplay() = delete; + + /** Driver Station display mode. */ + enum class Mode { + /** Display line mode. This is the default display mode. */ + Line, + /** Raw ANSI text mode. */ + RawAnsi + }; + + /** + * Sets the display mode. + * + * Mode::Line is the default display mode. Setting Mode::Line clears any + * pending display lines. Setting Mode::RawAnsi clears the display. + * + * @param mode display mode + */ + static void SetMode(Mode mode); + + /** + * Adds display data in line mode. + * + * Repeated calls with the same caption before UpdateLines() replace the + * previous line. The caption is used to identify the line and is not + * displayed. Empty or whitespace-only captions always append a new line. + * + * @param caption Line caption. + * @param line Line contents. + */ + static void AddData(std::string_view caption, std::string_view line); + + /** + * Adds an uncaptioned display line in line mode. + * + * This is equivalent to calling AddData() with an empty caption, which always + * appends a new line. + * + * @param line Line contents. + */ + static void AddLine(std::string_view line); + + /** + * Updates the display with all pending lines and clears the pending lines. + * + * Updates are sent at most once every 230 ms. If called before 230 ms has + * elapsed since the last update, the pending lines are cleared without + * sending. + */ + static void UpdateLines(); + + /** + * Writes ANSI text directly to the display in raw ANSI mode. + * + * This call is ignored unless the display is in raw ANSI mode. + * + * @param data ANSI text to write. + */ + static void WriteRawAnsi(std::string_view data); + + /** + * Clears the display in raw ANSI mode. + * + * This call is ignored unless the display is in raw ANSI mode. + */ + static void ClearRaw(); +}; + +} // namespace wpi diff --git a/wpilibc/src/main/python/pyproject.toml b/wpilibc/src/main/python/pyproject.toml index 1296443042..aed2ce283b 100644 --- a/wpilibc/src/main/python/pyproject.toml +++ b/wpilibc/src/main/python/pyproject.toml @@ -112,6 +112,7 @@ DriverStationBackend = "wpi/driverstation/internal/DriverStationBackend.hpp" Alliance = "wpi/driverstation/Alliance.hpp" Alert = "wpi/driverstation/Alert.hpp" DriverStation = "wpi/driverstation/DriverStation.hpp" +DriverStationDisplay = "wpi/driverstation/DriverStationDisplay.hpp" MatchState = "wpi/driverstation/MatchState.hpp" MatchType = "wpi/driverstation/MatchType.hpp" POVDirection = "wpi/driverstation/POVDirection.hpp" diff --git a/wpilibc/src/main/python/semiwrap/DriverStationDisplay.yml b/wpilibc/src/main/python/semiwrap/DriverStationDisplay.yml new file mode 100644 index 0000000000..87c67c985b --- /dev/null +++ b/wpilibc/src/main/python/semiwrap/DriverStationDisplay.yml @@ -0,0 +1,11 @@ +classes: + wpi::DriverStationDisplay: + enums: + Mode: + methods: + SetMode: + AddData: + AddLine: + UpdateLines: + WriteRawAnsi: + ClearRaw: diff --git a/wpilibc/src/main/python/wpilib/__init__.py b/wpilibc/src/main/python/wpilib/__init__.py index 6a083d1ea8..01dbfb970a 100644 --- a/wpilibc/src/main/python/wpilib/__init__.py +++ b/wpilibc/src/main/python/wpilib/__init__.py @@ -25,6 +25,7 @@ from ._wpilib import ( DoubleSolenoid, DriverStation, DriverStationBackend, + DriverStationDisplay, DualSenseController, DutyCycle, DutyCycleEncoder, @@ -142,6 +143,7 @@ __all__ = [ "DoubleSolenoid", "DriverStation", "DriverStationBackend", + "DriverStationDisplay", "DualSenseController", "DutyCycle", "DutyCycleEncoder", diff --git a/wpilibcExamples/example_projects.bzl b/wpilibcExamples/example_projects.bzl index af0bbb3520..9dd9b7da25 100644 --- a/wpilibcExamples/example_projects.bzl +++ b/wpilibcExamples/example_projects.bzl @@ -4,6 +4,8 @@ EXAMPLE_FOLDERS = [ "DifferentialDriveBot", "DifferentialDrivePoseEstimator", "DriveDistanceOffboard", + "DriverStationDisplayAnsi", + "DriverStationDisplayLines", "DutyCycleEncoder", "ElevatorExponentialProfile", "ElevatorExponentialSimulation", diff --git a/wpilibcExamples/src/main/cpp/examples/DriverStationDisplayAnsi/cpp/Robot.cpp b/wpilibcExamples/src/main/cpp/examples/DriverStationDisplayAnsi/cpp/Robot.cpp new file mode 100644 index 0000000000..39cfbcc0cc --- /dev/null +++ b/wpilibcExamples/src/main/cpp/examples/DriverStationDisplayAnsi/cpp/Robot.cpp @@ -0,0 +1,18 @@ +// Copyright (c) FIRST and other WPILib contributors. +// Open Source Software; you can modify and/or share it under the terms of +// the WPILib BSD license file in the root directory of this project. + +#include "Robot.hpp" + +#include "opmode/DefaultTeleop.hpp" + +Robot::Robot() { + AddOpMode(wpi::RobotMode::TELEOPERATED, "Default Teleop"); + PublishOpModes(); +} + +#ifndef RUNNING_WPILIB_TESTS +int main() { + return wpi::StartRobot(); +} +#endif diff --git a/wpilibcExamples/src/main/cpp/examples/DriverStationDisplayAnsi/cpp/opmode/DefaultTeleop.cpp b/wpilibcExamples/src/main/cpp/examples/DriverStationDisplayAnsi/cpp/opmode/DefaultTeleop.cpp new file mode 100644 index 0000000000..a8fed32d30 --- /dev/null +++ b/wpilibcExamples/src/main/cpp/examples/DriverStationDisplayAnsi/cpp/opmode/DefaultTeleop.cpp @@ -0,0 +1,47 @@ +// Copyright (c) FIRST and other WPILib contributors. +// Open Source Software; you can modify and/or share it under the terms of +// the WPILib BSD license file in the root directory of this project. + +#include "opmode/DefaultTeleop.hpp" + +#include +#include + +#include "wpi/driverstation/DriverStationDisplay.hpp" + +using namespace wpi::units::literals; + +namespace { +constexpr std::array COLORS = {196, 202, 208, 214, 220, 226, 118, + 46, 48, 51, 45, 39, 33, 27, + 21, 57, 93, 129, 165, 201}; +} // namespace + +void DefaultTeleop::Start() { + wpi::DriverStationDisplay::SetMode(wpi::DriverStationDisplay::Mode::RawAnsi); + m_timer.Restart(); + m_seconds = 0; + + wpi::DriverStationDisplay::WriteRawAnsi( + "\033[2J\033[H" + "DriverStationDisplay ANSI mode\n" + "This header and footer stay on screen.\n" + "Seconds elapsed: \n" + "Only the number above is rewritten."); + UpdateSecondsDisplay(); +} + +void DefaultTeleop::Periodic() { + if (m_timer.AdvanceIfElapsed(1_s)) { + ++m_seconds; + UpdateSecondsDisplay(); + } +} + +void DefaultTeleop::UpdateSecondsDisplay() { + int color = COLORS[m_seconds % COLORS.size()]; + std::array buffer; + std::snprintf(buffer.data(), buffer.size(), + "\033[3;18H\033[38;5;%dm%5d\033[0m", color, m_seconds); + wpi::DriverStationDisplay::WriteRawAnsi(buffer.data()); +} diff --git a/wpilibcExamples/src/main/cpp/examples/DriverStationDisplayAnsi/include/Robot.hpp b/wpilibcExamples/src/main/cpp/examples/DriverStationDisplayAnsi/include/Robot.hpp new file mode 100644 index 0000000000..8a0cfbd07e --- /dev/null +++ b/wpilibcExamples/src/main/cpp/examples/DriverStationDisplayAnsi/include/Robot.hpp @@ -0,0 +1,12 @@ +// Copyright (c) FIRST and other WPILib contributors. +// Open Source Software; you can modify and/or share it under the terms of +// the WPILib BSD license file in the root directory of this project. + +#pragma once + +#include "wpi/framework/OpModeRobot.hpp" + +class Robot : public wpi::OpModeRobot { + public: + Robot(); +}; diff --git a/wpilibcExamples/src/main/cpp/examples/DriverStationDisplayAnsi/include/opmode/DefaultTeleop.hpp b/wpilibcExamples/src/main/cpp/examples/DriverStationDisplayAnsi/include/opmode/DefaultTeleop.hpp new file mode 100644 index 0000000000..0427747aee --- /dev/null +++ b/wpilibcExamples/src/main/cpp/examples/DriverStationDisplayAnsi/include/opmode/DefaultTeleop.hpp @@ -0,0 +1,20 @@ +// Copyright (c) FIRST and other WPILib contributors. +// Open Source Software; you can modify and/or share it under the terms of +// the WPILib BSD license file in the root directory of this project. + +#pragma once + +#include "wpi/opmode/PeriodicOpMode.hpp" +#include "wpi/system/Timer.hpp" + +class DefaultTeleop : public wpi::PeriodicOpMode { + public: + void Start() override; + void Periodic() override; + + private: + void UpdateSecondsDisplay(); + + wpi::Timer m_timer; + int m_seconds = 0; +}; diff --git a/wpilibcExamples/src/main/cpp/examples/DriverStationDisplayLines/cpp/Robot.cpp b/wpilibcExamples/src/main/cpp/examples/DriverStationDisplayLines/cpp/Robot.cpp new file mode 100644 index 0000000000..39cfbcc0cc --- /dev/null +++ b/wpilibcExamples/src/main/cpp/examples/DriverStationDisplayLines/cpp/Robot.cpp @@ -0,0 +1,18 @@ +// Copyright (c) FIRST and other WPILib contributors. +// Open Source Software; you can modify and/or share it under the terms of +// the WPILib BSD license file in the root directory of this project. + +#include "Robot.hpp" + +#include "opmode/DefaultTeleop.hpp" + +Robot::Robot() { + AddOpMode(wpi::RobotMode::TELEOPERATED, "Default Teleop"); + PublishOpModes(); +} + +#ifndef RUNNING_WPILIB_TESTS +int main() { + return wpi::StartRobot(); +} +#endif diff --git a/wpilibcExamples/src/main/cpp/examples/DriverStationDisplayLines/cpp/opmode/DefaultTeleop.cpp b/wpilibcExamples/src/main/cpp/examples/DriverStationDisplayLines/cpp/opmode/DefaultTeleop.cpp new file mode 100644 index 0000000000..7ffde8a400 --- /dev/null +++ b/wpilibcExamples/src/main/cpp/examples/DriverStationDisplayLines/cpp/opmode/DefaultTeleop.cpp @@ -0,0 +1,29 @@ +// Copyright (c) FIRST and other WPILib contributors. +// Open Source Software; you can modify and/or share it under the terms of +// the WPILib BSD license file in the root directory of this project. + +#include "opmode/DefaultTeleop.hpp" + +#include +#include +#include + +#include "wpi/driverstation/DriverStationDisplay.hpp" + +void DefaultTeleop::Start() { + wpi::DriverStationDisplay::SetMode(wpi::DriverStationDisplay::Mode::Line); + m_timer.Restart(); + m_loopCount = 0; +} + +void DefaultTeleop::Periodic() { + std::ostringstream runtime; + runtime << std::fixed << std::setprecision(1) << m_timer.Get().value() + << " seconds"; + + wpi::DriverStationDisplay::AddLine("DriverStationDisplay line mode"); + wpi::DriverStationDisplay::AddData("Runtime", runtime.str()); + wpi::DriverStationDisplay::AddData("Loop Count", + std::to_string(m_loopCount++)); + wpi::DriverStationDisplay::UpdateLines(); +} diff --git a/wpilibcExamples/src/main/cpp/examples/DriverStationDisplayLines/include/Robot.hpp b/wpilibcExamples/src/main/cpp/examples/DriverStationDisplayLines/include/Robot.hpp new file mode 100644 index 0000000000..8a0cfbd07e --- /dev/null +++ b/wpilibcExamples/src/main/cpp/examples/DriverStationDisplayLines/include/Robot.hpp @@ -0,0 +1,12 @@ +// Copyright (c) FIRST and other WPILib contributors. +// Open Source Software; you can modify and/or share it under the terms of +// the WPILib BSD license file in the root directory of this project. + +#pragma once + +#include "wpi/framework/OpModeRobot.hpp" + +class Robot : public wpi::OpModeRobot { + public: + Robot(); +}; diff --git a/wpilibcExamples/src/main/cpp/examples/DriverStationDisplayLines/include/opmode/DefaultTeleop.hpp b/wpilibcExamples/src/main/cpp/examples/DriverStationDisplayLines/include/opmode/DefaultTeleop.hpp new file mode 100644 index 0000000000..9333684bec --- /dev/null +++ b/wpilibcExamples/src/main/cpp/examples/DriverStationDisplayLines/include/opmode/DefaultTeleop.hpp @@ -0,0 +1,18 @@ +// Copyright (c) FIRST and other WPILib contributors. +// Open Source Software; you can modify and/or share it under the terms of +// the WPILib BSD license file in the root directory of this project. + +#pragma once + +#include "wpi/opmode/PeriodicOpMode.hpp" +#include "wpi/system/Timer.hpp" + +class DefaultTeleop : public wpi::PeriodicOpMode { + public: + void Start() override; + void Periodic() override; + + private: + wpi::Timer m_timer; + int m_loopCount = 0; +}; diff --git a/wpilibcExamples/src/main/cpp/examples/examples.json b/wpilibcExamples/src/main/cpp/examples/examples.json index 82af623cbb..4b7e8a0d7b 100644 --- a/wpilibcExamples/src/main/cpp/examples/examples.json +++ b/wpilibcExamples/src/main/cpp/examples/examples.json @@ -250,6 +250,28 @@ "gradlebase": "cpp", "commandversion": 2 }, + { + "name": "Driver Station Display Lines", + "description": "Display lines on the Driver Station display and update them every robot loop.", + "tags": [ + "Basic Robot", + "Driver Station" + ], + "foldername": "DriverStationDisplayLines", + "gradlebase": "cpp", + "commandversion": 2 + }, + { + "name": "Driver Station Display ANSI", + "description": "Stream ANSI text to the Driver Station display and update a value in place once per second.", + "tags": [ + "Basic Robot", + "Driver Station" + ], + "foldername": "DriverStationDisplayAnsi", + "gradlebase": "cpp", + "commandversion": 2 + }, { "name": "DriveDistanceOffboard", "description": "Drive a differential drivetrain a set distance using TrapezoidProfile and smart motor controller PID.", diff --git a/wpilibj/src/main/java/org/wpilib/driverstation/DriverStationDisplay.java b/wpilibj/src/main/java/org/wpilib/driverstation/DriverStationDisplay.java new file mode 100644 index 0000000000..a82f2931e1 --- /dev/null +++ b/wpilibj/src/main/java/org/wpilib/driverstation/DriverStationDisplay.java @@ -0,0 +1,227 @@ +// Copyright (c) FIRST and other WPILib contributors. +// Open Source Software; you can modify and/or share it under the terms of +// the WPILib BSD license file in the root directory of this project. + +package org.wpilib.driverstation; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; +import org.wpilib.hardware.hal.DriverStationJNI; +import org.wpilib.util.WPIUtilJNI; + +/** + * Provides access to the Driver Station display. + * + *

Line mode is the default display mode. + */ +public final class DriverStationDisplay { + private static final long UPDATE_PERIOD_MICROS = 230_000L; + private static final String CLEAR_DISPLAY = "\033[0m\033[2J\033[H"; + + private static final Lock m_displayLock = new ReentrantLock(); + private static boolean rawMode; + private static final Map lineMap = new HashMap<>(); + private static final List lines = new ArrayList<>(); + private static long lastDisplayUpdate = WPIUtilJNI.now() - UPDATE_PERIOD_MICROS; + + private DriverStationDisplay() {} + + /** Driver Station display mode. */ + public enum Mode { + /** Display line mode. This is the default display mode. */ + Line, + + /** Raw ANSI text mode. */ + RawAnsi + } + + /** + * Sets the display mode. + * + *

{@link Mode#Line} is the default display mode. Setting {@link Mode#Line} clears any pending + * display lines. Setting {@link Mode#RawAnsi} clears the display. + * + * @param mode display mode + */ + public static void setMode(Mode mode) { + m_displayLock.lock(); + try { + switch (mode) { + case Line -> { + rawMode = false; + lineMap.clear(); + lines.clear(); + } + case RawAnsi -> { + rawMode = true; + DriverStationJNI.writeDisplayAnsi(CLEAR_DISPLAY); + } + default -> throw new IllegalArgumentException("Unknown display mode: " + mode); + } + } finally { + m_displayLock.unlock(); + } + } + + /** + * Adds display data in line mode. + * + *

Repeated calls with the same caption before {@link #updateLines()} replace the previous + * line. The caption is used to identify the line and is not displayed. Empty or whitespace-only + * captions always append a new line. + * + * @param caption Line caption. + * @param line Line contents. + */ + public static void addData(String caption, String line) { + m_displayLock.lock(); + try { + addDataUnderLock(caption, line); + } finally { + m_displayLock.unlock(); + } + } + + /** + * Adds display data in line mode. + * + *

Repeated calls with the same caption before {@link #updateLines()} replace the previous + * line. The caption is used to identify the line and is not displayed. Empty or whitespace-only + * captions always append a new line. + * + *

The value is converted to text with {@link String#valueOf(Object)}. + * + * @param caption Line caption. + * @param value Line value. + */ + public static void addData(String caption, Object value) { + addData(caption, String.valueOf(value)); + } + + /** + * Adds formatted display data in line mode. + * + *

Repeated calls with the same caption before {@link #updateLines()} replace the previous + * line. The caption is used to identify the line and is not displayed. Empty or whitespace-only + * captions always append a new line. + * + * @param caption Line caption. + * @param format Format string. + * @param args Format arguments. + */ + public static void addData(String caption, String format, Object... args) { + addData(caption, String.format(format, args)); + } + + private static void addDataUnderLock(String caption, String line) { + if (rawMode) { + return; + } + + String captionText = nonNull(caption); + String lineText = nonNull(line); + + if (captionText.isBlank()) { + lines.add(lineText); + return; + } + + Integer lineNum = lineMap.get(captionText); + if (lineNum == null) { + lineMap.put(captionText, lines.size()); + lines.add(lineText); + } else if (lineNum < lines.size()) { + lines.set(lineNum, lineText); + } + } + + /** + * Adds an uncaptioned display line in line mode. + * + *

This is equivalent to calling {@link #addData(String, String)} with an empty caption, which + * always appends a new line. + * + * @param line Line contents. + */ + public static void addLine(String line) { + addData("", line); + } + + /** + * Updates the display with all pending lines and clears the pending lines. + * + *

Updates are sent at most once every 230 ms. If called before 230 ms has elapsed since the + * last update, the pending lines are cleared without sending. + */ + public static void updateLines() { + m_displayLock.lock(); + try { + if (rawMode) { + return; + } + + long now = WPIUtilJNI.now(); + if (now - lastDisplayUpdate < UPDATE_PERIOD_MICROS) { + lineMap.clear(); + lines.clear(); + return; + } + lastDisplayUpdate = now; + + StringBuilder builder = new StringBuilder(); + builder.append(CLEAR_DISPLAY); + for (String line : lines) { + builder.append(line).append("\033[0m\n"); + } + + DriverStationJNI.writeDisplayAnsi(builder.toString()); + + lineMap.clear(); + lines.clear(); + } finally { + m_displayLock.unlock(); + } + } + + /** + * Writes ANSI text directly to the display in raw ANSI mode. + * + *

This call is ignored unless the display is in raw ANSI mode. + * + * @param data ANSI text to write. + */ + public static void writeRawAnsi(String data) { + m_displayLock.lock(); + try { + if (rawMode) { + DriverStationJNI.writeDisplayAnsi(nonNull(data)); + } + } finally { + m_displayLock.unlock(); + } + } + + /** + * Clears the display in raw ANSI mode. + * + *

This call is ignored unless the display is in raw ANSI mode. + */ + public static void clearRaw() { + m_displayLock.lock(); + try { + if (rawMode) { + DriverStationJNI.writeDisplayAnsi(CLEAR_DISPLAY); + } + } finally { + m_displayLock.unlock(); + } + } + + private static String nonNull(String value) { + return value == null ? "" : value; + } +} diff --git a/wpilibjExamples/example_projects.bzl b/wpilibjExamples/example_projects.bzl index d3b78e18b7..bc03b48695 100644 --- a/wpilibjExamples/example_projects.bzl +++ b/wpilibjExamples/example_projects.bzl @@ -4,6 +4,8 @@ EXAMPLE_FOLDERS = [ "differentialdrivebot", "differentialdriveposeestimator", "drivedistanceoffboard", + "driverstationdisplayansi", + "driverstationdisplaylines", "dutycycleencoder", "elevatorexponentialprofile", "elevatorexponentialsimulation", diff --git a/wpilibjExamples/src/main/java/org/wpilib/examples/driverstationdisplayansi/DefaultTeleop.java b/wpilibjExamples/src/main/java/org/wpilib/examples/driverstationdisplayansi/DefaultTeleop.java new file mode 100644 index 0000000000..499443fcce --- /dev/null +++ b/wpilibjExamples/src/main/java/org/wpilib/examples/driverstationdisplayansi/DefaultTeleop.java @@ -0,0 +1,53 @@ +// Copyright (c) FIRST and other WPILib contributors. +// Open Source Software; you can modify and/or share it under the terms of +// the WPILib BSD license file in the root directory of this project. + +package org.wpilib.examples.driverstationdisplayansi; + +import org.wpilib.driverstation.DriverStationDisplay; +import org.wpilib.driverstation.DriverStationDisplay.Mode; +import org.wpilib.opmode.PeriodicOpMode; +import org.wpilib.opmode.Teleop; +import org.wpilib.system.Timer; + +@Teleop(name = "Default Teleop") +public class DefaultTeleop extends PeriodicOpMode { + private static final int[] COLORS = { + 196, 202, 208, 214, 220, 226, 118, 46, 48, 51, + 45, 39, 33, 27, 21, 57, 93, 129, 165, 201 + }; + + private final Timer timer = new Timer(); + private int seconds; + + /** Called once when the robot is enabled. */ + @Override + public void start() { + DriverStationDisplay.setMode(Mode.RawAnsi); + timer.restart(); + seconds = 0; + + DriverStationDisplay.writeRawAnsi( + "\033[2J\033[H" + + "DriverStationDisplay ANSI mode\n" + + "This header and footer stay on screen.\n" + + "Seconds elapsed: \n" + + "Only the number above is rewritten."); + updateSecondsDisplay(); + } + + /** Called periodically while the robot is enabled. */ + @Override + public void periodic() { + if (timer.advanceIfElapsed(1.0)) { + seconds++; + updateSecondsDisplay(); + } + } + + private void updateSecondsDisplay() { + int color = COLORS[seconds % COLORS.length]; + DriverStationDisplay.writeRawAnsi( + String.format("\033[3;18H\033[38;5;%dm%5d\033[0m", color, seconds)); + } +} diff --git a/wpilibjExamples/src/main/java/org/wpilib/examples/driverstationdisplayansi/Robot.java b/wpilibjExamples/src/main/java/org/wpilib/examples/driverstationdisplayansi/Robot.java new file mode 100644 index 0000000000..0a85fc2720 --- /dev/null +++ b/wpilibjExamples/src/main/java/org/wpilib/examples/driverstationdisplayansi/Robot.java @@ -0,0 +1,13 @@ +// Copyright (c) FIRST and other WPILib contributors. +// Open Source Software; you can modify and/or share it under the terms of +// the WPILib BSD license file in the root directory of this project. + +package org.wpilib.examples.driverstationdisplayansi; + +import org.wpilib.framework.OpModeRobot; + +/** + * Demonstrates DriverStationDisplay raw ANSI mode. The default teleop opmode writes static display + * text once, then updates a value and its color in place once per second. + */ +public class Robot extends OpModeRobot {} diff --git a/wpilibjExamples/src/main/java/org/wpilib/examples/driverstationdisplaylines/DefaultTeleop.java b/wpilibjExamples/src/main/java/org/wpilib/examples/driverstationdisplaylines/DefaultTeleop.java new file mode 100644 index 0000000000..d16fdd4e76 --- /dev/null +++ b/wpilibjExamples/src/main/java/org/wpilib/examples/driverstationdisplaylines/DefaultTeleop.java @@ -0,0 +1,34 @@ +// Copyright (c) FIRST and other WPILib contributors. +// Open Source Software; you can modify and/or share it under the terms of +// the WPILib BSD license file in the root directory of this project. + +package org.wpilib.examples.driverstationdisplaylines; + +import org.wpilib.driverstation.DriverStationDisplay; +import org.wpilib.driverstation.DriverStationDisplay.Mode; +import org.wpilib.opmode.PeriodicOpMode; +import org.wpilib.opmode.Teleop; +import org.wpilib.system.Timer; + +@Teleop(name = "Default Teleop") +public class DefaultTeleop extends PeriodicOpMode { + private final Timer timer = new Timer(); + private int loopCount; + + /** Called once when the robot is enabled. */ + @Override + public void start() { + DriverStationDisplay.setMode(Mode.Line); + timer.restart(); + loopCount = 0; + } + + /** Called periodically while the robot is enabled. */ + @Override + public void periodic() { + DriverStationDisplay.addLine("DriverStationDisplay line mode"); + DriverStationDisplay.addData("Runtime", "%.1f seconds", timer.get()); + DriverStationDisplay.addData("Loop Count", "%d", loopCount++); + DriverStationDisplay.updateLines(); + } +} diff --git a/wpilibjExamples/src/main/java/org/wpilib/examples/driverstationdisplaylines/Robot.java b/wpilibjExamples/src/main/java/org/wpilib/examples/driverstationdisplaylines/Robot.java new file mode 100644 index 0000000000..38dd67f6f1 --- /dev/null +++ b/wpilibjExamples/src/main/java/org/wpilib/examples/driverstationdisplaylines/Robot.java @@ -0,0 +1,13 @@ +// Copyright (c) FIRST and other WPILib contributors. +// Open Source Software; you can modify and/or share it under the terms of +// the WPILib BSD license file in the root directory of this project. + +package org.wpilib.examples.driverstationdisplaylines; + +import org.wpilib.framework.OpModeRobot; + +/** + * Demonstrates DriverStationDisplay line mode. The default teleop opmode adds all lines each loop + * and updateLines() sends the pending set to the Driver Station display. + */ +public class Robot extends OpModeRobot {} diff --git a/wpilibjExamples/src/main/java/org/wpilib/examples/examples.json b/wpilibjExamples/src/main/java/org/wpilib/examples/examples.json index 4d7839cfde..15f6ab19d5 100644 --- a/wpilibjExamples/src/main/java/org/wpilib/examples/examples.json +++ b/wpilibjExamples/src/main/java/org/wpilib/examples/examples.json @@ -289,6 +289,30 @@ "robotclass": "Robot", "commandversion": 2 }, + { + "name": "Driver Station Display Lines", + "description": "Display lines on the Driver Station display and update them every robot loop.", + "tags": [ + "Basic Robot", + "Driver Station" + ], + "foldername": "driverstationdisplaylines", + "gradlebase": "java", + "robotclass": "Robot", + "commandversion": 2 + }, + { + "name": "Driver Station Display ANSI", + "description": "Stream ANSI text to the Driver Station display and update a value in place once per second.", + "tags": [ + "Basic Robot", + "Driver Station" + ], + "foldername": "driverstationdisplayansi", + "gradlebase": "java", + "robotclass": "Robot", + "commandversion": 2 + }, { "name": "DriveDistanceOffboard", "description": "Drive a differential drivetrain a set distance using TrapezoidProfile and smart motor controller PID.",