[hal,wpilib] Add DS Display API (#8975)

This commit is contained in:
Thad House
2026-06-20 10:28:24 -07:00
committed by GitHub
parent 481a586009
commit f1c9d82d50
35 changed files with 889 additions and 11 deletions

View File

@@ -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() {}
}

View File

@@ -3,6 +3,7 @@
// the WPILib BSD license file in the root directory of this project.
#include <atomic>
#include <chrono>
#include <cstdio>
#include <cstring>
#include <functional>
@@ -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 {

View File

@@ -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"

View File

@@ -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);
}

View File

@@ -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

View File

@@ -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();

View File

@@ -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();

View File

@@ -45,3 +45,4 @@ functions:
HAL_SetOpModeOptions:
HAL_ObserveUserProgram:
HAL_GetGameData:
HAL_WriteDisplayAnsi:

View File

@@ -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 = [

View File

@@ -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
################################################################################

View File

@@ -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",

View File

@@ -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

View File

@@ -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",

View File

@@ -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 <cctype>
#include <cstdint>
#include <string>
#include <string_view>
#include <vector>
#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<uint32_t> lineMap;
std::vector<std::string> 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<unsigned char>(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);
}
}

View File

@@ -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 <string_view>
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

View File

@@ -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"

View File

@@ -0,0 +1,11 @@
classes:
wpi::DriverStationDisplay:
enums:
Mode:
methods:
SetMode:
AddData:
AddLine:
UpdateLines:
WriteRawAnsi:
ClearRaw:

View File

@@ -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",

View File

@@ -4,6 +4,8 @@ EXAMPLE_FOLDERS = [
"DifferentialDriveBot",
"DifferentialDrivePoseEstimator",
"DriveDistanceOffboard",
"DriverStationDisplayAnsi",
"DriverStationDisplayLines",
"DutyCycleEncoder",
"ElevatorExponentialProfile",
"ElevatorExponentialSimulation",

View File

@@ -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<DefaultTeleop>(wpi::RobotMode::TELEOPERATED, "Default Teleop");
PublishOpModes();
}
#ifndef RUNNING_WPILIB_TESTS
int main() {
return wpi::StartRobot<Robot>();
}
#endif

View File

@@ -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 <array>
#include <cstdio>
#include "wpi/driverstation/DriverStationDisplay.hpp"
using namespace wpi::units::literals;
namespace {
constexpr std::array<int, 20> 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<char, 64> 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());
}

View File

@@ -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<Robot> {
public:
Robot();
};

View File

@@ -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;
};

View File

@@ -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<DefaultTeleop>(wpi::RobotMode::TELEOPERATED, "Default Teleop");
PublishOpModes();
}
#ifndef RUNNING_WPILIB_TESTS
int main() {
return wpi::StartRobot<Robot>();
}
#endif

View File

@@ -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 <iomanip>
#include <sstream>
#include <string>
#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();
}

View File

@@ -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<Robot> {
public:
Robot();
};

View File

@@ -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;
};

View File

@@ -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.",

View File

@@ -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.
*
* <p>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<String, Integer> lineMap = new HashMap<>();
private static final List<String> 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.
*
* <p>{@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.
*
* <p>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.
*
* <p>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.
*
* <p>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.
*
* <p>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.
*
* <p>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.
*
* <p>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.
*
* <p>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.
*
* <p>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;
}
}

View File

@@ -4,6 +4,8 @@ EXAMPLE_FOLDERS = [
"differentialdrivebot",
"differentialdriveposeestimator",
"drivedistanceoffboard",
"driverstationdisplayansi",
"driverstationdisplaylines",
"dutycycleencoder",
"elevatorexponentialprofile",
"elevatorexponentialsimulation",

View File

@@ -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));
}
}

View File

@@ -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 {}

View File

@@ -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();
}
}

View File

@@ -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 {}

View File

@@ -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.",