diff --git a/wpilibc/src/main/native/cpp/Alert.cpp b/wpilibc/src/main/native/cpp/Alert.cpp new file mode 100644 index 0000000000..86bfd68cb9 --- /dev/null +++ b/wpilibc/src/main/native/cpp/Alert.cpp @@ -0,0 +1,68 @@ +// 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 "frc/Alert.h" + +#include +#include + +#include + +using namespace frc; + +Alert::Alert(std::string_view text, AlertType type) + : Alert("Alerts", text, type) {} + +wpi::StringMap Alert::groups; + +Alert::Alert(std::string_view group, std::string_view text, AlertType type) + : m_type(type), m_text(text) { + if (!groups.contains(group)) { + groups[group] = SendableAlerts(); + frc::SmartDashboard::PutData(group, &groups[group]); + } + groups[group].m_alerts.push_back(std::shared_ptr(this)); +} + +void Alert::Set(bool active) { + if (active && !m_active) { + m_activeStartTime = frc::Timer::GetFPGATimestamp(); + } + m_active = active; +} + +void Alert::SetText(std::string_view text) { + m_text = text; +} + +void Alert::SendableAlerts::InitSendable(nt::NTSendableBuilder& builder) { + builder.SetSmartDashboardType("Alerts"); + builder.AddStringArrayProperty( + "errors", [this]() { return GetStrings(AlertType::kError); }, nullptr); + builder.AddStringArrayProperty( + "warnings", [this]() { return GetStrings(AlertType::kWarning); }, + nullptr); + builder.AddStringArrayProperty( + "infos", [this]() { return GetStrings(AlertType::kInfo); }, nullptr); +} + +std::vector Alert::SendableAlerts::GetStrings( + AlertType type) const { + wpi::SmallVector> alerts; + alerts.reserve(m_alerts.size()); + for (auto alert : m_alerts) { + if (alert->m_active && alert->m_type == type) { + alerts.push_back(alert); + } + } + std::sort(alerts.begin(), alerts.end(), [](const auto a, const auto b) { + return a->m_activeStartTime > b->m_activeStartTime; + }); + std::vector output{alerts.size()}; + for (unsigned int i = 0; i < alerts.size(); ++i) { + std::string text{alerts[i]->m_text}; + output[i] = text; + } + return output; +} diff --git a/wpilibc/src/main/native/include/frc/Alert.h b/wpilibc/src/main/native/include/frc/Alert.h new file mode 100644 index 0000000000..45dc4a66a2 --- /dev/null +++ b/wpilibc/src/main/native/include/frc/Alert.h @@ -0,0 +1,127 @@ +// 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 +#include +#include + +#include +#include +#include +#include +#include + +namespace frc { + +/** + * Persistent alert to be sent via NetworkTables. Alerts are tagged with a type + * of kError, kWarning, or kInfo to denote urgency. See Alert::AlertType for + * suggested usage of each type. Alerts can be displayed on supported + * dashboards, and are shown in a priority order based on type and recency of + * activation. + * + * Alerts should be created once and stored persistently, then updated to + * "active" or "inactive" as necessary. Set(bool) can be safely called + * periodically. + * + * This API is new for 2025, but is likely to change in future seasons to + * facilitate deeper integration with the robot control system. + * + *
+ * class Robot {
+ *   frc::Alert alert{"Something went wrong", frc::Alert::AlertType::kWarning};
+ * }
+ *
+ * Robot::periodic() {
+ *   alert.Set(...);
+ * }
+ * 
+ */ +class Alert { + public: + /** + * Represents an alert's level of urgency. + */ + enum class AlertType { + /** + * High priority alert - displayed first on the dashboard with a red "X" + * symbol. Use this type for problems which will seriously affect the + * robot's functionality and thus require immediate attention. + */ + kError, + + /** + * Medium priority alert - displayed second on the dashboard with a yellow + * "!" symbol. Use this type for problems which could affect the robot's + * functionality but do not necessarily require immediate attention. + */ + kWarning, + + /** + * Low priority alert - displayed last on the dashboard with a green "i" + * symbol. Use this type for problems which are unlikely to affect the + * robot's functionality, or any other alerts which do not fall under the + * other categories. + */ + kInfo + }; + + /** + * Creates a new alert in the default group - "Alerts". If this is the first + * to be instantiated, the appropriate entries will be added to NetworkTables. + * + * @param text Text to be displayed when the alert is active. + * @param type Alert urgency level. + */ + Alert(std::string_view text, AlertType type); + + /** + * Creates a new alert. If this is the first to be instantiated in its group, + * the appropriate entries will be added to NetworkTables. + * + * @param group Group identifier, used as the entry name in NetworkTables. + * @param text Text to be displayed when the alert is active. + * @param type Alert urgency level. + */ + Alert(std::string_view group, std::string_view text, AlertType type); + + /** + * Sets whether the alert should currently be displayed. When activated, the + * alert text will also be sent to the console. This method can be safely + * called periodically. + * + * @param active Whether to display the alert. + */ + void Set(bool active); + + /** + * Updates current alert text. Use this method to dynamically change the + * displayed alert, such as including more details about the detected problem. + * + * @param text Text to be displayed when the alert is active. + */ + void SetText(std::string_view text); + + private: + AlertType m_type; + bool m_active = false; + units::second_t m_activeStartTime; + std::string_view m_text; + + class SendableAlerts : public nt::NTSendable, + public wpi::SendableHelper { + public: + wpi::SmallVector> m_alerts; + void InitSendable(nt::NTSendableBuilder& builder) override; + + private: + std::vector GetStrings(AlertType type) const; + }; + + static wpi::StringMap groups; +}; + +} // namespace frc diff --git a/wpilibj/src/main/java/edu/wpi/first/wpilibj/Alert.java b/wpilibj/src/main/java/edu/wpi/first/wpilibj/Alert.java new file mode 100644 index 0000000000..fbd033d306 --- /dev/null +++ b/wpilibj/src/main/java/edu/wpi/first/wpilibj/Alert.java @@ -0,0 +1,150 @@ +// 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 edu.wpi.first.wpilibj; + +import edu.wpi.first.util.sendable.Sendable; +import edu.wpi.first.util.sendable.SendableBuilder; +import edu.wpi.first.wpilibj.smartdashboard.SmartDashboard; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; + +/** + * Persistent alert to be sent via NetworkTables. Alerts are tagged with a type of {@code kError}, + * {@code kWarning}, or {@code kInfo} to denote urgency. See {@link + * edu.wpi.first.wpilibj.Alert.AlertType AlertType} for suggested usage of each type. Alerts can be + * displayed on supported dashboards, and are shown in a priority order based on type and recency of + * activation. + * + *

Alerts should be created once and stored persistently, then updated to "active" or "inactive" + * as necessary. {@link #set(boolean)} can be safely called periodically. + * + *

This API is new for 2025, but is likely to change in future seasons to facilitate deeper + * integration with the robot control system. + * + *

+ * class Robot {
+ *   Alert alert = new Alert("Something went wrong", AlertType.kWarning);
+ *
+ *   periodic() {
+ *     alert.set(...);
+ *   }
+ * }
+ * 
+ * + *

Alternatively, alerts which are only used once at startup can be created and activated inline. + * + *

+ * public Robot() {
+ *   new Alert("Failed to load auto paths", AlertType.kError).set(true);
+ * }
+ * 
+ */ +public class Alert { + private static Map groups = new HashMap(); + + private final AlertType m_type; + private boolean m_active; + private double m_activeStartTime; + private String m_text; + + /** + * Creates a new alert in the default group - "Alerts". If this is the first to be instantiated, + * the appropriate entries will be added to NetworkTables. + * + * @param text Text to be displayed when the alert is active. + * @param type Alert urgency level. + */ + public Alert(String text, AlertType type) { + this("Alerts", text, type); + } + + /** + * Creates a new alert. If this is the first to be instantiated in its group, the appropriate + * entries will be added to NetworkTables. + * + * @param group Group identifier, used as the entry name in NetworkTables. + * @param text Text to be displayed when the alert is active. + * @param type Alert urgency level. + */ + public Alert(String group, String text, AlertType type) { + if (!groups.containsKey(group)) { + groups.put(group, new SendableAlerts()); + SmartDashboard.putData(group, groups.get(group)); + } + + m_text = text; + m_type = type; + groups.get(group).m_alerts.add(this); + } + + /** + * Sets whether the alert should currently be displayed. When activated, the alert text will also + * be sent to the console. This method can be safely called periodically. + * + * @param active Whether to display the alert. + */ + public void set(boolean active) { + if (active && !m_active) { + m_activeStartTime = Timer.getFPGATimestamp(); + } + m_active = active; + } + + /** + * Updates current alert text. Use this method to dynamically change the displayed alert, such as + * including more details about the detected problem. + * + * @param text Text to be displayed when the alert is active. + */ + public void setText(String text) { + m_text = text; + } + + private static final class SendableAlerts implements Sendable { + private final Collection m_alerts = new HashSet<>(); + + private String[] getStrings(AlertType type) { + return m_alerts.stream() + .filter((Alert a) -> a.m_active && a.m_type == type) + .sorted((Alert a, Alert b) -> Double.compare(b.m_activeStartTime, a.m_activeStartTime)) + .map((Alert a) -> a.m_text) + .toArray(String[]::new); + } + + @Override + public void initSendable(SendableBuilder builder) { + builder.setSmartDashboardType("Alerts"); + builder.addStringArrayProperty("errors", () -> getStrings(AlertType.kError), null); + builder.addStringArrayProperty("warnings", () -> getStrings(AlertType.kWarning), null); + builder.addStringArrayProperty("infos", () -> getStrings(AlertType.kInfo), null); + } + } + + /** Represents an alert's level of urgency. */ + public enum AlertType { + /** + * High priority alert - displayed first on the dashboard with a red "X" symbol. Use this type + * for problems which will seriously affect the robot's functionality and thus require immediate + * attention. + */ + kError, + + /** + * Medium priority alert - displayed second on the dashboard with a yellow "!" symbol. Use this + * type for problems which could affect the robot's functionality but do not necessarily require + * immediate attention. + */ + kWarning, + + /** + * Low priority alert - displayed last on the dashboard with a green "i" symbol. Use this type + * for problems which are unlikely to affect the robot's functionality, or any other alerts + * which do not fall under the other categories. + */ + kInfo + } +}