// 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 #include #include #include #include #include #include #include #include "frc/Errors.h" #include "frc/RobotController.h" #include "frc/smartdashboard/SmartDashboard.h" using namespace frc; class Alert::PublishedAlert { public: PublishedAlert(uint64_t timestamp, std::string_view text) : timestamp{timestamp}, text{text} {} uint64_t timestamp; std::string text; auto operator<=>(const PublishedAlert& other) const { if (timestamp != other.timestamp) { return other.timestamp <=> timestamp; } else { return text <=> other.text; } } }; class Alert::SendableAlerts : public nt::NTSendable, public wpi::SendableHelper { public: SendableAlerts() { m_alerts.fill({}); } void InitSendable(nt::NTSendableBuilder& builder) override { 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); } /** * Returns a reference to the set of active alerts for the given type. * @param type the type * @return reference to the set of active alerts for the type */ std::set& GetActiveAlertsStorage(AlertType type) { return const_cast&>( std::as_const(*this).GetActiveAlertsStorage(type)); } const std::set& GetActiveAlertsStorage(AlertType type) const { switch (type) { case AlertType::kInfo: case AlertType::kWarning: case AlertType::kError: return m_alerts[static_cast(type)]; default: throw FRC_MakeError(frc::err::InvalidParameter, "Invalid Alert Type: {}", type); } } /** * Returns the SendableAlerts for a given group, initializing and publishing * if it does not already exist. * @param group the group name * @return the SendableAlerts for the group */ static SendableAlerts& ForGroup(std::string_view group) { SendableAlerts* salert = nullptr; try { auto* sendable = frc::SmartDashboard::GetData(group); salert = dynamic_cast(sendable); } catch (frc::RuntimeError&) { } if (!salert) { // this leaks if ResetSmartDashboardInstance is called, but that's fine salert = new Alert::SendableAlerts; frc::SmartDashboard::PutData(group, salert); } return *salert; } private: std::vector GetStrings(AlertType type) const { auto& set = GetActiveAlertsStorage(type); std::vector output; output.reserve(set.size()); for (auto& alert : set) { output.emplace_back(alert.text); } return output; } std::array, 3> m_alerts; }; Alert::Alert(std::string_view text, AlertType type) : Alert("Alerts", text, type) {} Alert::Alert(std::string_view group, std::string_view text, AlertType type) : m_type(type), m_text(text), m_activeAlerts{ &SendableAlerts::ForGroup(group).GetActiveAlertsStorage(m_type)} {} Alert::Alert(Alert&& other) : m_type{other.m_type}, m_text{std::move(other.m_text)}, m_activeAlerts{std::exchange(other.m_activeAlerts, nullptr)}, m_active{std::exchange(other.m_active, false)}, m_activeStartTime{other.m_activeStartTime} {} Alert& Alert::operator=(Alert&& other) { if (&other != this) { // We want to destroy current state after the move is done Alert tmp{std::move(*this)}; // Now, swap moved-from state with other state std::swap(m_type, other.m_type); std::swap(m_text, other.m_text); std::swap(m_activeAlerts, other.m_activeAlerts); std::swap(m_active, other.m_active); std::swap(m_activeStartTime, other.m_activeStartTime); } return *this; } Alert::~Alert() { Set(false); } void Alert::Set(bool active) { if (active == m_active) { return; } if (active) { m_activeStartTime = frc::RobotController::GetTime(); m_activeAlerts->emplace(m_activeStartTime, m_text); } else { m_activeAlerts->erase({m_activeStartTime, m_text}); } m_active = active; } void Alert::SetText(std::string_view text) { if (text == m_text) { return; } std::string oldText = std::move(m_text); m_text = text; if (m_active) { auto iter = m_activeAlerts->find({m_activeStartTime, oldText}); auto hint = m_activeAlerts->erase(iter); m_activeAlerts->emplace_hint(hint, m_activeStartTime, m_text); } } std::string frc::format_as(Alert::AlertType type) { switch (type) { case Alert::AlertType::kInfo: return "kInfo"; case Alert::AlertType::kWarning: return "kWarning"; case Alert::AlertType::kError: return "kError"; default: return std::to_string(static_cast(type)); } }