mirror of
https://github.com/wpilibsuite/allwpilib
synced 2026-06-19 00:41:43 +00:00
[hal,wpilib] Move Alert to HAL (#8646)
SystemCore implementation is not yet connected to MRCComm.
This commit is contained in:
94
hal/src/main/java/org/wpilib/hardware/hal/AlertJNI.java
Normal file
94
hal/src/main/java/org/wpilib/hardware/hal/AlertJNI.java
Normal file
@@ -0,0 +1,94 @@
|
||||
// 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.hardware.hal;
|
||||
|
||||
/**
|
||||
* The AlertJNI class directly wraps the C++ HAL Alert.
|
||||
*
|
||||
* <p>This class is not meant for direct use by teams. Instead, the org.wpilib.driverstation.Alert
|
||||
* class, which corresponds to the C++ Alert class, should be used.
|
||||
*
|
||||
* @see "wpi/hal/Alert.h"
|
||||
*/
|
||||
public class AlertJNI extends JNIWrapper {
|
||||
/**
|
||||
* High priority alert - displayed first with a red "X" symbol. Use this type for problems which
|
||||
* will seriously affect the robot's functionality and thus require immediate attention.
|
||||
*/
|
||||
public static final int LEVEL_HIGH = 0;
|
||||
|
||||
/**
|
||||
* Medium priority alert - displayed second with a yellow "!" symbol. Use this type for problems
|
||||
* which could affect the robot's functionality but do not necessarily require immediate
|
||||
* attention.
|
||||
*/
|
||||
public static final int LEVEL_MEDIUM = 1;
|
||||
|
||||
/**
|
||||
* Low priority alert - displayed last 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.
|
||||
*/
|
||||
public static final int LEVEL_LOW = 2;
|
||||
|
||||
/**
|
||||
* Creates an alert.
|
||||
*
|
||||
* @param group Group identifier
|
||||
* @param text Text to be displayed when the alert is active
|
||||
* @param level Alert urgency level (LEVEL_HIGH, LEVEL_MEDIUM, LEVEL_LOW)
|
||||
* @return the created alert handle
|
||||
* @see "HAL_CreateAlert"
|
||||
*/
|
||||
public static native int createAlert(String group, String text, int level);
|
||||
|
||||
/**
|
||||
* Destroys an alert.
|
||||
*
|
||||
* @param alertHandle the alert handle
|
||||
* @see "HAL_DestroyAlert"
|
||||
*/
|
||||
public static native void destroyAlert(int alertHandle);
|
||||
|
||||
/**
|
||||
* Sets whether the alert should be displayed. This method can be safely be called periodically.
|
||||
*
|
||||
* @param alertHandle the alert handle
|
||||
* @param active true to display the alert, false to hide it
|
||||
* @see "HAL_SetAlertActive"
|
||||
*/
|
||||
public static native void setAlertActive(int alertHandle, boolean active);
|
||||
|
||||
/**
|
||||
* Checks if an alert is active.
|
||||
*
|
||||
* @param alertHandle the alert handle
|
||||
* @return true if the alert is active
|
||||
* @see "HAL_IsAlertActive"
|
||||
*/
|
||||
public static native boolean isAlertActive(int alertHandle);
|
||||
|
||||
/**
|
||||
* Sets the text of the alert. Use this method to dynamically change the displayed alert, such as
|
||||
* including more details about the detected problem.
|
||||
*
|
||||
* @param alertHandle Alert handle.
|
||||
* @param text new text to be displayed when the alert is active
|
||||
* @see "HAL_SetAlertText"
|
||||
*/
|
||||
public static native void setAlertText(int alertHandle, String text);
|
||||
|
||||
/**
|
||||
* Gets the text of the alert.
|
||||
*
|
||||
* @param alertHandle Alert handle.
|
||||
* @return the text displayed when the alert is active
|
||||
* @see "HAL_GetAlertText"
|
||||
*/
|
||||
public static native String getAlertText(int alertHandle);
|
||||
|
||||
/** Utility class. */
|
||||
private AlertJNI() {}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
// 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.hardware.hal.simulation;
|
||||
|
||||
import org.wpilib.hardware.hal.JNIWrapper;
|
||||
|
||||
/** JNI for alert data. */
|
||||
public class AlertDataJNI extends JNIWrapper {
|
||||
/** Information about an alert. */
|
||||
public static class AlertInfo {
|
||||
public AlertInfo(int handle, String group, String text, long activeStartTime, int level) {
|
||||
this.handle = handle;
|
||||
this.group = group;
|
||||
this.text = text;
|
||||
this.activeStartTime = activeStartTime;
|
||||
this.level = level;
|
||||
}
|
||||
|
||||
@SuppressWarnings("MemberName")
|
||||
public final int handle;
|
||||
|
||||
@SuppressWarnings("MemberName")
|
||||
public final String group;
|
||||
|
||||
@SuppressWarnings("MemberName")
|
||||
public final String text;
|
||||
|
||||
@SuppressWarnings("MemberName")
|
||||
public final long activeStartTime; // 0 if not active
|
||||
|
||||
@SuppressWarnings("MemberName")
|
||||
public final int level; // ALERT_LEVEL_HIGH, ALERT_LEVEL_MEDIUM, ALERT_LEVEL_LOW
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the number of alerts. Note: this is not guaranteed to be consistent with the number of
|
||||
* alerts returned by getAlerts, so the latter's return value should be used to determine how many
|
||||
* alerts were actually filled in.
|
||||
*
|
||||
* @return the number of alerts
|
||||
*/
|
||||
public static native int getNumAlerts();
|
||||
|
||||
/**
|
||||
* Gets detailed information about each alert.
|
||||
*
|
||||
* @return Array of information about each alert
|
||||
*/
|
||||
public static native AlertInfo[] getAlerts();
|
||||
|
||||
/** Resets all alert simulation data. */
|
||||
public static native void resetData();
|
||||
|
||||
/** Utility class. */
|
||||
private AlertDataJNI() {}
|
||||
}
|
||||
124
hal/src/main/native/cpp/jni/AlertJNI.cpp
Normal file
124
hal/src/main/native/cpp/jni/AlertJNI.cpp
Normal file
@@ -0,0 +1,124 @@
|
||||
// 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 <jni.h>
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdio>
|
||||
|
||||
#include "HALUtil.h"
|
||||
#include "org_wpilib_hardware_hal_AlertJNI.h"
|
||||
#include "wpi/hal/Alert.h"
|
||||
#include "wpi/util/jni_util.hpp"
|
||||
#include "wpi/util/string.h"
|
||||
|
||||
using namespace wpi::hal;
|
||||
using namespace wpi::util::java;
|
||||
|
||||
extern "C" {
|
||||
|
||||
/*
|
||||
* Class: org_wpilib_hardware_hal_AlertJNI
|
||||
* Method: createAlert
|
||||
* Signature: (Ljava/lang/String;Ljava/lang/String;I)I
|
||||
*/
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_org_wpilib_hardware_hal_AlertJNI_createAlert
|
||||
(JNIEnv* env, jclass, jstring group, jstring text, jint level)
|
||||
{
|
||||
int32_t status = 0;
|
||||
wpi::util::java::JStringRef jgroup{env, group};
|
||||
wpi::util::java::JStringRef jtext{env, text};
|
||||
WPI_String wpiGroup = wpi::util::make_string(jgroup);
|
||||
WPI_String wpiText = wpi::util::make_string(jtext);
|
||||
HAL_AlertHandle alertHandle =
|
||||
HAL_CreateAlert(&wpiGroup, &wpiText, level, &status);
|
||||
|
||||
if (alertHandle <= 0 || !CheckStatusForceThrow(env, status)) {
|
||||
return 0; // something went wrong in HAL
|
||||
}
|
||||
|
||||
return (jint)alertHandle;
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: org_wpilib_hardware_hal_AlertJNI
|
||||
* Method: destroyAlert
|
||||
* Signature: (I)V
|
||||
*/
|
||||
JNIEXPORT void JNICALL
|
||||
Java_org_wpilib_hardware_hal_AlertJNI_destroyAlert
|
||||
(JNIEnv* env, jclass, jint alertHandle)
|
||||
{
|
||||
if (alertHandle != HAL_kInvalidHandle) {
|
||||
HAL_DestroyAlert((HAL_AlertHandle)alertHandle);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: org_wpilib_hardware_hal_AlertJNI
|
||||
* Method: setAlertActive
|
||||
* Signature: (IZ)V
|
||||
*/
|
||||
JNIEXPORT void JNICALL
|
||||
Java_org_wpilib_hardware_hal_AlertJNI_setAlertActive
|
||||
(JNIEnv* env, jclass cls, jint alertHandle, jboolean active)
|
||||
{
|
||||
int32_t status = 0;
|
||||
HAL_SetAlertActive((HAL_AlertHandle)alertHandle, active, &status);
|
||||
CheckStatus(env, status);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: org_wpilib_hardware_hal_AlertJNI
|
||||
* Method: isAlertActive
|
||||
* Signature: (I)Z
|
||||
*/
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_org_wpilib_hardware_hal_AlertJNI_isAlertActive
|
||||
(JNIEnv* env, jclass cls, jint alertHandle)
|
||||
{
|
||||
int32_t status = 0;
|
||||
jboolean active = HAL_IsAlertActive((HAL_AlertHandle)alertHandle, &status);
|
||||
CheckStatus(env, status);
|
||||
return active;
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: org_wpilib_hardware_hal_AlertJNI
|
||||
* Method: setAlertText
|
||||
* Signature: (ILjava/lang/String;)V
|
||||
*/
|
||||
JNIEXPORT void JNICALL
|
||||
Java_org_wpilib_hardware_hal_AlertJNI_setAlertText
|
||||
(JNIEnv* env, jclass cls, jint alertHandle, jstring text)
|
||||
{
|
||||
int32_t status = 0;
|
||||
wpi::util::java::JStringRef jtext{env, text};
|
||||
WPI_String wpiText = wpi::util::make_string(jtext);
|
||||
HAL_SetAlertText((HAL_AlertHandle)alertHandle, &wpiText, &status);
|
||||
CheckStatus(env, status);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: org_wpilib_hardware_hal_AlertJNI
|
||||
* Method: getAlertText
|
||||
* Signature: (I)Ljava/lang/String;
|
||||
*/
|
||||
JNIEXPORT jstring JNICALL
|
||||
Java_org_wpilib_hardware_hal_AlertJNI_getAlertText
|
||||
(JNIEnv* env, jclass cls, jint alertHandle)
|
||||
{
|
||||
int32_t status = 0;
|
||||
WPI_String text;
|
||||
HAL_GetAlertText((HAL_AlertHandle)alertHandle, &text, &status);
|
||||
if (!CheckStatus(env, status)) {
|
||||
return nullptr;
|
||||
}
|
||||
jstring rv = MakeJString(env, wpi::util::to_string_view(&text));
|
||||
WPI_FreeString(&text);
|
||||
return rv;
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
91
hal/src/main/native/cpp/jni/simulation/AlertDataJNI.cpp
Normal file
91
hal/src/main/native/cpp/jni/simulation/AlertDataJNI.cpp
Normal file
@@ -0,0 +1,91 @@
|
||||
// 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 "AlertDataJNI.hpp"
|
||||
|
||||
#include <jni.h>
|
||||
|
||||
#include "org_wpilib_hardware_hal_simulation_AlertDataJNI.h"
|
||||
#include "wpi/hal/simulation/AlertData.h"
|
||||
#include "wpi/util/jni_util.hpp"
|
||||
|
||||
using namespace wpi::util::java;
|
||||
|
||||
static JClass alertInfoCls;
|
||||
|
||||
static jobject MakeAlertInfoJava(JNIEnv* env, const HALSIM_AlertInfo& info) {
|
||||
static jmethodID func = env->GetMethodID(
|
||||
alertInfoCls, "<init>", "(ILjava/lang/String;Ljava/lang/String;JI)V");
|
||||
return env->NewObject(
|
||||
alertInfoCls, func, static_cast<jint>(info.handle),
|
||||
MakeJString(env, wpi::util::to_string_view(&info.group)),
|
||||
MakeJString(env, wpi::util::to_string_view(&info.text)),
|
||||
static_cast<jlong>(info.activeStartTime), static_cast<jint>(info.level));
|
||||
}
|
||||
namespace wpi::hal::sim {
|
||||
|
||||
bool InitializeAlertDataJNI(JNIEnv* env) {
|
||||
alertInfoCls =
|
||||
JClass(env, "org/wpilib/hardware/hal/simulation/AlertDataJNI$AlertInfo");
|
||||
if (!alertInfoCls) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void FreeAlertDataJNI(JNIEnv* env) {
|
||||
alertInfoCls.free(env);
|
||||
}
|
||||
|
||||
} // namespace wpi::hal::sim
|
||||
|
||||
extern "C" {
|
||||
|
||||
/*
|
||||
* Class: org_wpilib_hardware_hal_simulation_AlertDataJNI
|
||||
* Method: getNumAlerts
|
||||
* Signature: ()I
|
||||
*/
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_org_wpilib_hardware_hal_simulation_AlertDataJNI_getNumAlerts
|
||||
(JNIEnv*, jclass)
|
||||
{
|
||||
return HALSIM_GetNumAlerts();
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: org_wpilib_hardware_hal_simulation_AlertDataJNI
|
||||
* Method: getAlerts
|
||||
* Signature: ()[Ljava/lang/Object;
|
||||
*/
|
||||
JNIEXPORT jobjectArray JNICALL
|
||||
Java_org_wpilib_hardware_hal_simulation_AlertDataJNI_getAlerts
|
||||
(JNIEnv* env, jclass)
|
||||
{
|
||||
int32_t allocLen = HALSIM_GetNumAlerts();
|
||||
HALSIM_AlertInfo* arr = new HALSIM_AlertInfo[allocLen];
|
||||
int32_t len = HALSIM_GetAlerts(arr, allocLen);
|
||||
|
||||
jobjectArray ret = env->NewObjectArray(len, alertInfoCls, nullptr);
|
||||
for (int32_t i = 0; i < len; ++i) {
|
||||
env->SetObjectArrayElement(ret, i, MakeAlertInfoJava(env, arr[i]));
|
||||
}
|
||||
HALSIM_FreeAlerts(arr, len < allocLen ? len : allocLen);
|
||||
delete[] arr;
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: org_wpilib_hardware_hal_simulation_AlertDataJNI
|
||||
* Method: resetData
|
||||
* Signature: ()V
|
||||
*/
|
||||
JNIEXPORT void JNICALL
|
||||
Java_org_wpilib_hardware_hal_simulation_AlertDataJNI_resetData
|
||||
(JNIEnv*, jclass)
|
||||
{
|
||||
HALSIM_ResetAlertData();
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
12
hal/src/main/native/cpp/jni/simulation/AlertDataJNI.hpp
Normal file
12
hal/src/main/native/cpp/jni/simulation/AlertDataJNI.hpp
Normal 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 <jni.h>
|
||||
|
||||
namespace wpi::hal::sim {
|
||||
bool InitializeAlertDataJNI(JNIEnv* env);
|
||||
void FreeAlertDataJNI(JNIEnv* env);
|
||||
} // namespace wpi::hal::sim
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#include "SimulatorJNI.h"
|
||||
|
||||
#include "AlertDataJNI.hpp"
|
||||
#include "BufferCallbackStore.h"
|
||||
#include "CallbackStore.h"
|
||||
#include "ConstBufferCallbackStore.h"
|
||||
@@ -78,6 +79,9 @@ jint SimOnLoad(JavaVM* vm, void* reserved) {
|
||||
InitializeBufferStore();
|
||||
InitializeConstBufferStore();
|
||||
InitializeOpModeOptionsStore();
|
||||
if (!InitializeAlertDataJNI(env)) {
|
||||
return JNI_ERR;
|
||||
}
|
||||
if (!InitializeSimDeviceDataJNI(env)) {
|
||||
return JNI_ERR;
|
||||
}
|
||||
@@ -95,6 +99,7 @@ void SimOnUnload(JavaVM* vm, void* reserved) {
|
||||
bufferCallbackCls.free(env);
|
||||
constBufferCallbackCls.free(env);
|
||||
biConsumerCls.free(env);
|
||||
FreeAlertDataJNI(env);
|
||||
FreeSimDeviceDataJNI(env);
|
||||
jvm = nullptr;
|
||||
}
|
||||
|
||||
113
hal/src/main/native/include/wpi/hal/Alert.h
Normal file
113
hal/src/main/native/include/wpi/hal/Alert.h
Normal file
@@ -0,0 +1,113 @@
|
||||
// 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 <stdint.h>
|
||||
|
||||
#include "wpi/hal/Types.h"
|
||||
#include "wpi/util/string.h"
|
||||
|
||||
/**
|
||||
* @defgroup hal_alert Alert Functions
|
||||
* @ingroup hal_capi
|
||||
* @{
|
||||
*/
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** Represents an alert's level of urgency. */
|
||||
HAL_ENUM(HAL_AlertLevel) {
|
||||
/**
|
||||
* High priority alert - displayed first with a red "X" symbol. Use this type
|
||||
* for problems which will seriously affect the robot's functionality and thus
|
||||
* require immediate attention.
|
||||
*/
|
||||
HAL_ALERT_HIGH = 0,
|
||||
HAL_ALERT_ERROR = HAL_ALERT_HIGH,
|
||||
|
||||
/**
|
||||
* Medium priority alert - displayed second with a yellow "!" symbol. Use this
|
||||
* type for problems which could affect the robot's functionality but do not
|
||||
* necessarily require immediate attention.
|
||||
*/
|
||||
HAL_ALERT_MEDIUM = 1,
|
||||
HAL_ALERT_WARNING = HAL_ALERT_MEDIUM,
|
||||
|
||||
/**
|
||||
* Low priority alert - displayed last 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.
|
||||
*/
|
||||
HAL_ALERT_LOW = 2,
|
||||
HAL_ALERT_INFO = HAL_ALERT_LOW
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates an alert.
|
||||
*
|
||||
* @param group Group identifier
|
||||
* @param text Text to be displayed when the alert is active
|
||||
* @param level Alert urgency level (HAL_AlertLevel)
|
||||
* @param[out] status Error status variable. 0 on success.
|
||||
* @return the created alert
|
||||
*/
|
||||
HAL_AlertHandle HAL_CreateAlert(const struct WPI_String* group,
|
||||
const struct WPI_String* text, int32_t level,
|
||||
int32_t* status);
|
||||
|
||||
/**
|
||||
* Destroys an alert.
|
||||
*
|
||||
* @param alertHandle the alert handle
|
||||
*/
|
||||
void HAL_DestroyAlert(HAL_AlertHandle alertHandle);
|
||||
|
||||
/**
|
||||
* Sets whether the alert should be displayed. This method can be
|
||||
* safely be called periodically.
|
||||
*
|
||||
* @param alertHandle the alert handle
|
||||
* @param active true to display the alert, false to hide it
|
||||
* @param[out] status Error status variable. 0 on success.
|
||||
*/
|
||||
void HAL_SetAlertActive(HAL_AlertHandle alertHandle, HAL_Bool active,
|
||||
int32_t* status);
|
||||
|
||||
/**
|
||||
* Checks if an alert is active.
|
||||
*
|
||||
* @param alertHandle the alert handle
|
||||
* @param[out] status Error status variable. 0 on success.
|
||||
* @return true if the alert is active
|
||||
*/
|
||||
HAL_Bool HAL_IsAlertActive(HAL_AlertHandle alertHandle, int32_t* status);
|
||||
|
||||
/**
|
||||
* Updates the text of an alert. Use this method to dynamically change the
|
||||
* displayed alert, such as including more details about the detected problem.
|
||||
*
|
||||
* @param alertHandle the alert handle
|
||||
* @param text new text to be displayed when the alert is active
|
||||
* @param[out] status Error status variable. 0 on success.
|
||||
*/
|
||||
void HAL_SetAlertText(HAL_AlertHandle alertHandle,
|
||||
const struct WPI_String* text, int32_t* status);
|
||||
|
||||
/**
|
||||
* Gets the text of an alert.
|
||||
*
|
||||
* @param alertHandle the alert handle
|
||||
* @param text pointer to a WPI_String to be filled with the current text
|
||||
* @param[out] status Error status variable. 0 on success.
|
||||
*/
|
||||
void HAL_GetAlertText(HAL_AlertHandle alertHandle, struct WPI_String* text,
|
||||
int32_t* status);
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif
|
||||
/** @} */
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
typedef int32_t HAL_Handle;
|
||||
|
||||
typedef HAL_Handle HAL_AlertHandle;
|
||||
|
||||
typedef HAL_Handle HAL_AnalogInputHandle;
|
||||
|
||||
typedef HAL_Handle HAL_AnalogOutputHandle;
|
||||
|
||||
@@ -72,6 +72,7 @@ enum class HAL_HandleEnum {
|
||||
REVPDH = 26,
|
||||
REVPH = 27,
|
||||
CANStream = 28,
|
||||
Alert = 29,
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
60
hal/src/main/native/include/wpi/hal/simulation/AlertData.h
Normal file
60
hal/src/main/native/include/wpi/hal/simulation/AlertData.h
Normal file
@@ -0,0 +1,60 @@
|
||||
// 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 <stdint.h>
|
||||
|
||||
#include "wpi/hal/Types.h"
|
||||
#include "wpi/util/string.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** Information about an alert. */
|
||||
struct HALSIM_AlertInfo {
|
||||
HAL_AlertHandle handle;
|
||||
struct WPI_String group;
|
||||
struct WPI_String text;
|
||||
int64_t activeStartTime; // 0 if not active
|
||||
int32_t level; // HAL_AlertLevel
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the number of alerts. Note: this is not guaranteed to be consistent
|
||||
* with the number of alerts returned by HALSIM_GetAlerts, so the latter's
|
||||
* return value should be used to determine how many alerts were actually filled
|
||||
* in.
|
||||
*
|
||||
* @return the number of alerts
|
||||
*/
|
||||
int32_t HALSIM_GetNumAlerts(void);
|
||||
|
||||
/**
|
||||
* Gets detailed information about each alert.
|
||||
*
|
||||
* @param arr array of information to be filled
|
||||
* @param length length of arr
|
||||
* @return Number of alerts; note: may be larger or smaller than passed-in
|
||||
* length
|
||||
*/
|
||||
int32_t HALSIM_GetAlerts(struct HALSIM_AlertInfo* arr, int32_t length);
|
||||
|
||||
/**
|
||||
* Frees an array of alert information returned by HALSIM_GetAlerts.
|
||||
*
|
||||
* @param arr array to free
|
||||
* @param length number of alerts in arr (as returned by HALSIM_GetAlerts)
|
||||
*/
|
||||
void HALSIM_FreeAlerts(struct HALSIM_AlertInfo* arr, int32_t length);
|
||||
|
||||
/**
|
||||
* Resets all alert simulation data.
|
||||
*/
|
||||
void HALSIM_ResetAlertData(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif
|
||||
155
hal/src/main/native/sim/Alert.cpp
Normal file
155
hal/src/main/native/sim/Alert.cpp
Normal file
@@ -0,0 +1,155 @@
|
||||
// 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/hal/Alert.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "HALInitializer.h"
|
||||
#include "wpi/hal/Errors.h"
|
||||
#include "wpi/hal/HALBase.h"
|
||||
#include "wpi/hal/Types.h"
|
||||
#include "wpi/hal/handles/UnlimitedHandleResource.h"
|
||||
#include "wpi/hal/simulation/AlertData.h"
|
||||
#include "wpi/util/mutex.hpp"
|
||||
#include "wpi/util/string.h"
|
||||
|
||||
namespace {
|
||||
struct Alert {
|
||||
Alert(std::string_view group, std::string_view text, int32_t level)
|
||||
: group{group}, text{text}, level{level} {}
|
||||
|
||||
wpi::util::mutex textMutex;
|
||||
std::string group;
|
||||
std::string text;
|
||||
std::atomic<int64_t> activeStartTime{0}; // non-zero when active
|
||||
int32_t level = 0;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
using namespace wpi::hal;
|
||||
|
||||
static UnlimitedHandleResource<HAL_AlertHandle, Alert, HAL_HandleEnum::Alert>*
|
||||
alertHandles;
|
||||
|
||||
namespace wpi::hal::init {
|
||||
void InitializeAlert() {
|
||||
static UnlimitedHandleResource<HAL_AlertHandle, Alert, HAL_HandleEnum::Alert>
|
||||
aH;
|
||||
alertHandles = &aH;
|
||||
}
|
||||
} // namespace wpi::hal::init
|
||||
|
||||
extern "C" {
|
||||
|
||||
HAL_AlertHandle HAL_CreateAlert(const WPI_String* group, const WPI_String* text,
|
||||
int32_t level, int32_t* status) {
|
||||
wpi::hal::init::CheckInit();
|
||||
std::shared_ptr<Alert> alert = std::make_shared<Alert>(
|
||||
wpi::util::to_string_view(group), wpi::util::to_string_view(text), level);
|
||||
HAL_AlertHandle handle = alertHandles->Allocate(alert);
|
||||
if (handle == HAL_kInvalidHandle) {
|
||||
*status = HAL_HANDLE_ERROR;
|
||||
return HAL_kInvalidHandle;
|
||||
}
|
||||
return handle;
|
||||
}
|
||||
|
||||
void HAL_DestroyAlert(HAL_AlertHandle alertHandle) {
|
||||
alertHandles->Free(alertHandle);
|
||||
}
|
||||
|
||||
void HAL_SetAlertActive(HAL_AlertHandle alertHandle, HAL_Bool active,
|
||||
int32_t* status) {
|
||||
auto alert = alertHandles->Get(alertHandle);
|
||||
if (!alert) {
|
||||
*status = HAL_HANDLE_ERROR;
|
||||
return;
|
||||
}
|
||||
if (active) {
|
||||
if (alert->activeStartTime.load(std::memory_order_relaxed) != 0) {
|
||||
// Already active, do nothing (avoids cost of getting time)
|
||||
return;
|
||||
}
|
||||
int64_t now = HAL_GetFPGATime(status);
|
||||
int64_t expected = 0;
|
||||
// use compare-exchange to avoid potential race
|
||||
alert->activeStartTime.compare_exchange_strong(expected, now);
|
||||
} else {
|
||||
alert->activeStartTime = 0;
|
||||
}
|
||||
}
|
||||
|
||||
HAL_Bool HAL_IsAlertActive(HAL_AlertHandle alertHandle, int32_t* status) {
|
||||
auto alert = alertHandles->Get(alertHandle);
|
||||
if (!alert) {
|
||||
*status = HAL_HANDLE_ERROR;
|
||||
return false;
|
||||
}
|
||||
return alert->activeStartTime != 0;
|
||||
}
|
||||
|
||||
void HAL_SetAlertText(HAL_AlertHandle alertHandle, const WPI_String* text,
|
||||
int32_t* status) {
|
||||
auto alert = alertHandles->Get(alertHandle);
|
||||
if (!alert) {
|
||||
*status = HAL_HANDLE_ERROR;
|
||||
return;
|
||||
}
|
||||
std::scoped_lock lock(alert->textMutex);
|
||||
alert->text = wpi::util::to_string_view(text);
|
||||
}
|
||||
|
||||
void HAL_GetAlertText(HAL_AlertHandle alertHandle, struct WPI_String* text,
|
||||
int32_t* status) {
|
||||
auto alert = alertHandles->Get(alertHandle);
|
||||
if (alert) {
|
||||
std::scoped_lock lock(alert->textMutex);
|
||||
*text = wpi::util::alloc_wpi_string(alert->text);
|
||||
} else {
|
||||
*status = HAL_HANDLE_ERROR;
|
||||
*text = WPI_String{};
|
||||
}
|
||||
}
|
||||
|
||||
int32_t HALSIM_GetNumAlerts(void) {
|
||||
int32_t count = 0;
|
||||
alertHandles->ForEach([&](auto, auto) { ++count; });
|
||||
return count;
|
||||
}
|
||||
|
||||
int32_t HALSIM_GetAlerts(struct HALSIM_AlertInfo* arr, int32_t length) {
|
||||
int32_t num = 0;
|
||||
alertHandles->ForEach([&](HAL_AlertHandle handle, Alert* alert) {
|
||||
if (num < length) {
|
||||
arr[num].handle = handle;
|
||||
arr[num].group = wpi::util::alloc_wpi_string(alert->group);
|
||||
{
|
||||
std::scoped_lock lock(alert->textMutex);
|
||||
arr[num].text = wpi::util::alloc_wpi_string(alert->text);
|
||||
}
|
||||
arr[num].activeStartTime =
|
||||
alert->activeStartTime.load(std::memory_order_relaxed);
|
||||
arr[num].level = alert->level;
|
||||
}
|
||||
++num;
|
||||
});
|
||||
return num;
|
||||
}
|
||||
|
||||
void HALSIM_FreeAlerts(struct HALSIM_AlertInfo* arr, int32_t length) {
|
||||
for (int32_t i = 0; i < length; ++i) {
|
||||
WPI_FreeString(&arr[i].group);
|
||||
WPI_FreeString(&arr[i].text);
|
||||
}
|
||||
}
|
||||
|
||||
void HALSIM_ResetAlertData(void) {
|
||||
alertHandles->ResetHandles();
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
@@ -82,6 +82,7 @@ void InitializeHAL() {
|
||||
InitializeRoboRioData();
|
||||
InitializeSimDeviceData();
|
||||
InitializeAddressableLED();
|
||||
InitializeAlert();
|
||||
InitializeAnalogInput();
|
||||
InitializeAnalogInternal();
|
||||
InitializeCAN();
|
||||
|
||||
@@ -34,6 +34,7 @@ extern void InitializePWMData();
|
||||
extern void InitializeRoboRioData();
|
||||
extern void InitializeSimDeviceData();
|
||||
extern void InitializeAddressableLED();
|
||||
extern void InitializeAlert();
|
||||
extern void InitializeAnalogInput();
|
||||
extern void InitializeAnalogInternal();
|
||||
extern void InitializeCAN();
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#include "../PortsInternal.h"
|
||||
#include "wpi/hal/simulation/AddressableLEDData.h"
|
||||
#include "wpi/hal/simulation/AlertData.h"
|
||||
#include "wpi/hal/simulation/AnalogInData.h"
|
||||
#include "wpi/hal/simulation/CTREPCMData.h"
|
||||
#include "wpi/hal/simulation/CanData.h"
|
||||
@@ -70,4 +71,5 @@ extern "C" void HALSIM_ResetAllSimData(void) {
|
||||
|
||||
HALSIM_ResetRoboRioData();
|
||||
HALSIM_ResetSimDeviceData();
|
||||
HALSIM_ResetAlertData();
|
||||
}
|
||||
|
||||
131
hal/src/main/native/systemcore/Alert.cpp
Normal file
131
hal/src/main/native/systemcore/Alert.cpp
Normal file
@@ -0,0 +1,131 @@
|
||||
// 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/hal/Alert.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "HALInitializer.h"
|
||||
#include "wpi/hal/Errors.h"
|
||||
#include "wpi/hal/HALBase.h"
|
||||
#include "wpi/hal/Types.h"
|
||||
#include "wpi/hal/handles/UnlimitedHandleResource.h"
|
||||
#include "wpi/hal/simulation/AlertData.h"
|
||||
#include "wpi/util/mutex.hpp"
|
||||
#include "wpi/util/string.h"
|
||||
|
||||
namespace {
|
||||
struct Alert {
|
||||
Alert(std::string_view group, std::string_view text, int32_t level)
|
||||
: group{group}, text{text}, level{level} {}
|
||||
|
||||
wpi::util::mutex textMutex;
|
||||
std::string group;
|
||||
std::string text;
|
||||
std::atomic<int64_t> activeStartTime{0}; // non-zero when active
|
||||
int32_t level = 0;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
using namespace wpi::hal;
|
||||
|
||||
static UnlimitedHandleResource<HAL_AlertHandle, Alert, HAL_HandleEnum::Alert>*
|
||||
alertHandles;
|
||||
|
||||
namespace wpi::hal::init {
|
||||
void InitializeAlert() {
|
||||
static UnlimitedHandleResource<HAL_AlertHandle, Alert, HAL_HandleEnum::Alert>
|
||||
aH;
|
||||
alertHandles = &aH;
|
||||
}
|
||||
} // namespace wpi::hal::init
|
||||
|
||||
extern "C" {
|
||||
|
||||
HAL_AlertHandle HAL_CreateAlert(const WPI_String* group, const WPI_String* text,
|
||||
int32_t level, int32_t* status) {
|
||||
wpi::hal::init::CheckInit();
|
||||
std::shared_ptr<Alert> alert = std::make_shared<Alert>(
|
||||
wpi::util::to_string_view(group), wpi::util::to_string_view(text), level);
|
||||
HAL_AlertHandle handle = alertHandles->Allocate(alert);
|
||||
if (handle == HAL_kInvalidHandle) {
|
||||
*status = HAL_HANDLE_ERROR;
|
||||
return HAL_kInvalidHandle;
|
||||
}
|
||||
return handle;
|
||||
}
|
||||
|
||||
void HAL_DestroyAlert(HAL_AlertHandle alertHandle) {
|
||||
alertHandles->Free(alertHandle);
|
||||
}
|
||||
|
||||
void HAL_SetAlertActive(HAL_AlertHandle alertHandle, HAL_Bool active,
|
||||
int32_t* status) {
|
||||
auto alert = alertHandles->Get(alertHandle);
|
||||
if (!alert) {
|
||||
*status = HAL_HANDLE_ERROR;
|
||||
return;
|
||||
}
|
||||
if (active) {
|
||||
if (alert->activeStartTime.load(std::memory_order_relaxed) != 0) {
|
||||
// Already active, do nothing (avoids cost of getting time)
|
||||
return;
|
||||
}
|
||||
int64_t now = HAL_GetFPGATime(status);
|
||||
int64_t expected = 0;
|
||||
// use compare-exchange to avoid potential race
|
||||
alert->activeStartTime.compare_exchange_strong(expected, now);
|
||||
} else {
|
||||
alert->activeStartTime = 0;
|
||||
}
|
||||
}
|
||||
|
||||
HAL_Bool HAL_IsAlertActive(HAL_AlertHandle alertHandle, int32_t* status) {
|
||||
auto alert = alertHandles->Get(alertHandle);
|
||||
if (!alert) {
|
||||
*status = HAL_HANDLE_ERROR;
|
||||
return false;
|
||||
}
|
||||
return alert->activeStartTime != 0;
|
||||
}
|
||||
|
||||
void HAL_SetAlertText(HAL_AlertHandle alertHandle, const WPI_String* text,
|
||||
int32_t* status) {
|
||||
auto alert = alertHandles->Get(alertHandle);
|
||||
if (!alert) {
|
||||
*status = HAL_HANDLE_ERROR;
|
||||
return;
|
||||
}
|
||||
std::scoped_lock lock(alert->textMutex);
|
||||
alert->text = wpi::util::to_string_view(text);
|
||||
}
|
||||
|
||||
void HAL_GetAlertText(HAL_AlertHandle alertHandle, struct WPI_String* text,
|
||||
int32_t* status) {
|
||||
auto alert = alertHandles->Get(alertHandle);
|
||||
if (alert) {
|
||||
std::scoped_lock lock(alert->textMutex);
|
||||
*text = wpi::util::alloc_wpi_string(alert->text);
|
||||
} else {
|
||||
*status = HAL_HANDLE_ERROR;
|
||||
*text = WPI_String{};
|
||||
}
|
||||
}
|
||||
|
||||
int32_t HALSIM_GetNumAlerts(void) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int32_t HALSIM_GetAlerts(struct HALSIM_AlertInfo* arr, int32_t length) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
void HALSIM_FreeAlerts(struct HALSIM_AlertInfo* arr, int32_t length) {}
|
||||
|
||||
void HALSIM_ResetAlertData(void) {}
|
||||
|
||||
} // extern "C"
|
||||
31
wpilibc/robotpy_pybind_build_info.bzl
generated
31
wpilibc/robotpy_pybind_build_info.bzl
generated
@@ -96,6 +96,16 @@ def wpilib_extension(srcs = [], header_to_dat_deps = [], extra_hdrs = [], includ
|
||||
("wpi::RobotDriveBase", "wpi__RobotDriveBase.hpp"),
|
||||
],
|
||||
),
|
||||
struct(
|
||||
class_name = "Alert",
|
||||
yml_file = "semiwrap/Alert.yml",
|
||||
header_root = "$(execpath :robotpy-native-wpilib.copy_headers)",
|
||||
header_file = "$(execpath :robotpy-native-wpilib.copy_headers)/wpi/driverstation/Alert.hpp",
|
||||
tmpl_class_names = [],
|
||||
trampolines = [
|
||||
("wpi::Alert", "wpi__Alert.hpp"),
|
||||
],
|
||||
),
|
||||
struct(
|
||||
class_name = "DriverStation",
|
||||
yml_file = "semiwrap/DriverStation.yml",
|
||||
@@ -960,16 +970,6 @@ def wpilib_extension(srcs = [], header_to_dat_deps = [], extra_hdrs = [], includ
|
||||
("wpi::Watchdog", "wpi__Watchdog.hpp"),
|
||||
],
|
||||
),
|
||||
struct(
|
||||
class_name = "Alert",
|
||||
yml_file = "semiwrap/Alert.yml",
|
||||
header_root = "$(execpath :robotpy-native-wpilib.copy_headers)",
|
||||
header_file = "$(execpath :robotpy-native-wpilib.copy_headers)/wpi/util/Alert.hpp",
|
||||
tmpl_class_names = [],
|
||||
trampolines = [
|
||||
("wpi::Alert", "wpi__Alert.hpp"),
|
||||
],
|
||||
),
|
||||
struct(
|
||||
class_name = "Preferences",
|
||||
yml_file = "semiwrap/Preferences.yml",
|
||||
@@ -1106,6 +1106,17 @@ def wpilib_simulation_extension(srcs = [], header_to_dat_deps = [], extra_hdrs =
|
||||
("wpi::sim::AddressableLEDSim", "wpi__sim__AddressableLEDSim.hpp"),
|
||||
],
|
||||
),
|
||||
struct(
|
||||
class_name = "AlertSim",
|
||||
yml_file = "semiwrap/simulation/AlertSim.yml",
|
||||
header_root = "$(execpath :robotpy-native-wpilib.copy_headers)",
|
||||
header_file = "$(execpath :robotpy-native-wpilib.copy_headers)/wpi/simulation/AlertSim.hpp",
|
||||
tmpl_class_names = [],
|
||||
trampolines = [
|
||||
("wpi::sim::AlertSim", "wpi__sim__AlertSim.hpp"),
|
||||
("wpi::sim::AlertSim::AlertInfo", "wpi__sim__AlertSim__AlertInfo.hpp"),
|
||||
],
|
||||
),
|
||||
struct(
|
||||
class_name = "AnalogEncoderSim",
|
||||
yml_file = "semiwrap/simulation/AnalogEncoderSim.yml",
|
||||
|
||||
52
wpilibc/src/main/native/cpp/driverstation/Alert.cpp
Normal file
52
wpilibc/src/main/native/cpp/driverstation/Alert.cpp
Normal file
@@ -0,0 +1,52 @@
|
||||
// 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/Alert.hpp"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "wpi/util/string.h"
|
||||
|
||||
using namespace wpi;
|
||||
|
||||
static HAL_AlertHandle CreateAlert(std::string_view group,
|
||||
std::string_view text, Alert::Level level) {
|
||||
WPI_String wpiGroup = wpi::util::make_string(group);
|
||||
WPI_String wpiText = wpi::util::make_string(text);
|
||||
int32_t status = 0;
|
||||
return HAL_CreateAlert(&wpiGroup, &wpiText, static_cast<int32_t>(level),
|
||||
&status);
|
||||
}
|
||||
|
||||
Alert::Alert(std::string_view text, Level type) : Alert("Alerts", text, type) {}
|
||||
|
||||
Alert::Alert(std::string_view group, std::string_view text, Level type)
|
||||
: m_handle{CreateAlert(group, text, type)} {}
|
||||
|
||||
void Alert::Set(bool active) {
|
||||
int32_t status = 0;
|
||||
HAL_SetAlertActive(m_handle, active, &status);
|
||||
}
|
||||
|
||||
bool Alert::Get() const {
|
||||
int32_t status = 0;
|
||||
return HAL_IsAlertActive(m_handle, &status);
|
||||
}
|
||||
|
||||
void Alert::SetText(std::string_view text) {
|
||||
WPI_String wpiText = wpi::util::make_string(text);
|
||||
int32_t status = 0;
|
||||
HAL_SetAlertText(m_handle, &wpiText, &status);
|
||||
}
|
||||
|
||||
std::string Alert::GetText() const {
|
||||
WPI_String wpiText;
|
||||
int32_t status = 0;
|
||||
HAL_GetAlertText(m_handle, &wpiText, &status);
|
||||
std::string rv{wpiText.str, wpiText.len};
|
||||
WPI_FreeString(&wpiText);
|
||||
return rv;
|
||||
}
|
||||
40
wpilibc/src/main/native/cpp/simulation/AlertSim.cpp
Normal file
40
wpilibc/src/main/native/cpp/simulation/AlertSim.cpp
Normal file
@@ -0,0 +1,40 @@
|
||||
// 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/simulation/AlertSim.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "wpi/hal/simulation/AlertData.h"
|
||||
#include "wpi/util/string.h"
|
||||
|
||||
using namespace wpi;
|
||||
using namespace wpi::sim;
|
||||
|
||||
int32_t AlertSim::GetCount() {
|
||||
return HALSIM_GetNumAlerts();
|
||||
}
|
||||
|
||||
std::vector<AlertSim::AlertInfo> AlertSim::GetAll() {
|
||||
int32_t allocLen = HALSIM_GetNumAlerts();
|
||||
HALSIM_AlertInfo* cInfos = new HALSIM_AlertInfo[allocLen];
|
||||
int32_t len = HALSIM_GetAlerts(cInfos, allocLen);
|
||||
std::vector<AlertInfo> infos;
|
||||
infos.reserve(len);
|
||||
for (int32_t i = 0; i < len; ++i) {
|
||||
const auto& cInfo = cInfos[i];
|
||||
infos.emplace_back(
|
||||
cInfo.handle, std::string{wpi::util::to_string_view(&cInfo.group)},
|
||||
std::string{wpi::util::to_string_view(&cInfo.text)},
|
||||
cInfo.activeStartTime, static_cast<Alert::Level>(cInfo.level));
|
||||
}
|
||||
HALSIM_FreeAlerts(cInfos, len < allocLen ? len : allocLen);
|
||||
delete[] cInfos;
|
||||
return infos;
|
||||
}
|
||||
|
||||
void AlertSim::ResetData() {
|
||||
HALSIM_ResetAlertData();
|
||||
}
|
||||
@@ -1,187 +0,0 @@
|
||||
// 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/util/Alert.hpp"
|
||||
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include "wpi/nt/NTSendable.hpp"
|
||||
#include "wpi/nt/NTSendableBuilder.hpp"
|
||||
#include "wpi/smartdashboard/SmartDashboard.hpp"
|
||||
#include "wpi/system/Errors.hpp"
|
||||
#include "wpi/system/RobotController.hpp"
|
||||
#include "wpi/util/StringMap.hpp"
|
||||
#include "wpi/util/sendable/SendableHelper.hpp"
|
||||
#include "wpi/util/sendable/SendableRegistry.hpp"
|
||||
|
||||
using namespace wpi;
|
||||
|
||||
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 wpi::nt::NTSendable,
|
||||
public wpi::util::SendableHelper<SendableAlerts> {
|
||||
public:
|
||||
SendableAlerts() { m_alerts.fill({}); }
|
||||
|
||||
void InitSendable(wpi::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<PublishedAlert>& GetActiveAlertsStorage(AlertType type) {
|
||||
return const_cast<std::set<Alert::PublishedAlert>&>(
|
||||
std::as_const(*this).GetActiveAlertsStorage(type));
|
||||
}
|
||||
|
||||
const std::set<PublishedAlert>& GetActiveAlertsStorage(AlertType type) const {
|
||||
switch (type) {
|
||||
case AlertType::kInfo:
|
||||
case AlertType::kWarning:
|
||||
case AlertType::kError:
|
||||
return m_alerts[static_cast<int32_t>(type)];
|
||||
default:
|
||||
throw WPILIB_MakeError(wpi::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 = wpi::SmartDashboard::GetData(group);
|
||||
salert = dynamic_cast<SendableAlerts*>(sendable);
|
||||
} catch (wpi::RuntimeError&) {
|
||||
}
|
||||
if (!salert) {
|
||||
// this leaks if ResetSmartDashboardInstance is called, but that's fine
|
||||
salert = new Alert::SendableAlerts;
|
||||
wpi::SmartDashboard::PutData(group, salert);
|
||||
}
|
||||
return *salert;
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<std::string> GetStrings(AlertType type) const {
|
||||
auto& set = GetActiveAlertsStorage(type);
|
||||
std::vector<std::string> output;
|
||||
output.reserve(set.size());
|
||||
for (auto& alert : set) {
|
||||
output.emplace_back(alert.text);
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
std::array<std::set<PublishedAlert>, 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 = wpi::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 wpi::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<int>(type));
|
||||
}
|
||||
}
|
||||
@@ -4,30 +4,28 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#include "wpi/hal/Alert.h"
|
||||
#include "wpi/hal/Types.h"
|
||||
|
||||
namespace wpi {
|
||||
|
||||
/**
|
||||
* 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, with newly activated alerts first.
|
||||
* Persistent alert to be sent to the driver station. Alerts are tagged with a
|
||||
* type of HIGH/ERROR, MEDIUM/WARNING, or LOW/INFO to denote urgency. See
|
||||
* Alert::Level 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, with newly activated alerts first.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* <pre>
|
||||
* class Robot {
|
||||
* wpi::Alert alert{"Something went wrong", wpi::Alert::AlertType::kWarning};
|
||||
* wpi::Alert alert{"Something went wrong", wpi::Alert::Level::WARNING};
|
||||
* }
|
||||
*
|
||||
* Robot::periodic() {
|
||||
@@ -40,28 +38,36 @@ class Alert {
|
||||
/**
|
||||
* Represents an alert's level of urgency.
|
||||
*/
|
||||
enum class AlertType {
|
||||
enum class Level {
|
||||
/**
|
||||
* High priority alert - displayed first on the dashboard with a red "X"
|
||||
* High priority alert - displayed first with a red "X"
|
||||
* symbol. Use this type for problems which will seriously affect the
|
||||
* robot's functionality and thus require immediate attention.
|
||||
*/
|
||||
kError,
|
||||
HIGH = HAL_ALERT_HIGH,
|
||||
|
||||
/** Alternate name for a high priority alert. */
|
||||
ERROR = HIGH,
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* Medium priority alert - displayed second with a yellow "!" symbol.
|
||||
* Use this type for problems which could affect the robot's functionality
|
||||
* but do not necessarily require immediate attention.
|
||||
*/
|
||||
kWarning,
|
||||
MEDIUM = HAL_ALERT_MEDIUM,
|
||||
|
||||
/** Alternate name for a medium priority alert. */
|
||||
WARNING = MEDIUM,
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* Low priority alert - displayed last 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
|
||||
LOW = HAL_ALERT_LOW,
|
||||
|
||||
/** Alternate name for a low priority alert. */
|
||||
INFO = LOW
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -69,9 +75,9 @@ class Alert {
|
||||
* 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.
|
||||
* @param level Alert urgency level.
|
||||
*/
|
||||
Alert(std::string_view text, AlertType type);
|
||||
Alert(std::string_view text, Level level);
|
||||
|
||||
/**
|
||||
* Creates a new alert. If this is the first to be instantiated in its group,
|
||||
@@ -79,17 +85,9 @@ class Alert {
|
||||
*
|
||||
* @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.
|
||||
* @param level Alert urgency level.
|
||||
*/
|
||||
Alert(std::string_view group, std::string_view text, AlertType type);
|
||||
|
||||
Alert(Alert&&);
|
||||
Alert& operator=(Alert&&);
|
||||
|
||||
Alert(const Alert&) = default;
|
||||
Alert& operator=(const Alert&) = default;
|
||||
|
||||
~Alert();
|
||||
Alert(std::string_view group, std::string_view text, Level level);
|
||||
|
||||
/**
|
||||
* Sets whether the alert should currently be displayed. This method can be
|
||||
@@ -103,7 +101,7 @@ class Alert {
|
||||
* Gets whether the alert is active.
|
||||
* @return whether the alert is active.
|
||||
*/
|
||||
bool Get() const { return m_active; }
|
||||
bool Get() const;
|
||||
|
||||
/**
|
||||
* Updates current alert text. Use this method to dynamically change the
|
||||
@@ -117,25 +115,17 @@ class Alert {
|
||||
* Gets the current alert text.
|
||||
* @return the current text.
|
||||
*/
|
||||
std::string GetText() const { return m_text; }
|
||||
std::string GetText() const;
|
||||
|
||||
/**
|
||||
* Get the type of this alert.
|
||||
* @return the type
|
||||
*/
|
||||
AlertType GetType() const { return m_type; }
|
||||
Level GetType() const { return m_type; }
|
||||
|
||||
private:
|
||||
class PublishedAlert;
|
||||
class SendableAlerts;
|
||||
|
||||
AlertType m_type;
|
||||
std::string m_text;
|
||||
std::set<PublishedAlert>* m_activeAlerts;
|
||||
bool m_active = false;
|
||||
uint64_t m_activeStartTime;
|
||||
Level m_type;
|
||||
wpi::hal::Handle<HAL_AlertHandle, HAL_DestroyAlert> m_handle;
|
||||
};
|
||||
|
||||
std::string format_as(Alert::AlertType type);
|
||||
|
||||
} // namespace wpi
|
||||
70
wpilibc/src/main/native/include/wpi/simulation/AlertSim.hpp
Normal file
70
wpilibc/src/main/native/include/wpi/simulation/AlertSim.hpp
Normal file
@@ -0,0 +1,70 @@
|
||||
// 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 <stdint.h>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "wpi/driverstation/Alert.hpp"
|
||||
#include "wpi/hal/Types.h"
|
||||
|
||||
namespace wpi::sim {
|
||||
|
||||
/**
|
||||
* Class to get info on simulated alerts.
|
||||
*/
|
||||
class AlertSim final {
|
||||
public:
|
||||
AlertSim() = delete;
|
||||
|
||||
/** Information about an alert. */
|
||||
struct AlertInfo {
|
||||
/** The handle of the alert. */
|
||||
HAL_AlertHandle handle;
|
||||
|
||||
/** The group of the alert. */
|
||||
std::string group;
|
||||
|
||||
/** The text of the alert. */
|
||||
std::string text;
|
||||
|
||||
/** The time the alert became active. 0 if not active. */
|
||||
int64_t activeStartTime;
|
||||
|
||||
/** The level of the alert (HIGH, MEDIUM, or LOW). */
|
||||
Alert::Level level;
|
||||
|
||||
/**
|
||||
* Returns whether the alert is currently active.
|
||||
*
|
||||
* @return true if the alert is active, false otherwise
|
||||
*/
|
||||
bool isActive() const { return activeStartTime != 0; }
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the number of alerts. Note: this is not guaranteed to be consistent
|
||||
* with the number of alerts returned by GetAll.
|
||||
*
|
||||
* @return the number of alerts
|
||||
*/
|
||||
static int32_t GetCount();
|
||||
|
||||
/**
|
||||
* Gets detailed information about each alert.
|
||||
*
|
||||
* @return Alerts
|
||||
*/
|
||||
static std::vector<AlertInfo> GetAll();
|
||||
|
||||
/**
|
||||
* Resets all alert simulation data.
|
||||
*/
|
||||
static void ResetData();
|
||||
};
|
||||
|
||||
} // namespace wpi::sim
|
||||
@@ -105,6 +105,7 @@ MecanumDrive = "wpi/drive/MecanumDrive.hpp"
|
||||
RobotDriveBase = "wpi/drive/RobotDriveBase.hpp"
|
||||
|
||||
# wpi/driverstation
|
||||
Alert = "wpi/driverstation/Alert.hpp"
|
||||
DriverStation = "wpi/driverstation/DriverStation.hpp"
|
||||
Gamepad = "wpi/driverstation/Gamepad.hpp"
|
||||
GenericHID = "wpi/driverstation/GenericHID.hpp"
|
||||
@@ -228,7 +229,6 @@ Tracer = "wpi/system/Tracer.hpp"
|
||||
Watchdog = "wpi/system/Watchdog.hpp"
|
||||
|
||||
# wpi/util
|
||||
Alert = "wpi/util/Alert.hpp"
|
||||
Preferences = "wpi/util/Preferences.hpp"
|
||||
SensorUtil = "wpi/util/SensorUtil.hpp"
|
||||
|
||||
@@ -243,6 +243,7 @@ yaml_path = "semiwrap/simulation"
|
||||
# wpi/simulation
|
||||
ADXL345Sim = "wpi/simulation/ADXL345Sim.hpp"
|
||||
AddressableLEDSim = "wpi/simulation/AddressableLEDSim.hpp"
|
||||
AlertSim = "wpi/simulation/AlertSim.hpp"
|
||||
AnalogEncoderSim = "wpi/simulation/AnalogEncoderSim.hpp"
|
||||
AnalogInputSim = "wpi/simulation/AnalogInputSim.hpp"
|
||||
BatterySim = "wpi/simulation/BatterySim.hpp"
|
||||
|
||||
@@ -1,16 +1,12 @@
|
||||
functions:
|
||||
format_as:
|
||||
ignore: true
|
||||
|
||||
classes:
|
||||
wpi::Alert:
|
||||
enums:
|
||||
AlertType:
|
||||
Level:
|
||||
methods:
|
||||
Alert:
|
||||
overloads:
|
||||
std::string_view, AlertType:
|
||||
std::string_view, std::string_view, AlertType:
|
||||
std::string_view, Level:
|
||||
std::string_view, std::string_view, Level:
|
||||
Set:
|
||||
Get:
|
||||
SetText:
|
||||
|
||||
24
wpilibc/src/main/python/semiwrap/simulation/AlertSim.yml
Normal file
24
wpilibc/src/main/python/semiwrap/simulation/AlertSim.yml
Normal file
@@ -0,0 +1,24 @@
|
||||
classes:
|
||||
wpi::sim::AlertSim:
|
||||
methods:
|
||||
GetCount:
|
||||
GetAll:
|
||||
ResetData:
|
||||
wpi::sim::AlertSim::AlertInfo:
|
||||
attributes:
|
||||
handle:
|
||||
group:
|
||||
text:
|
||||
activeStartTime:
|
||||
level:
|
||||
ignore: true
|
||||
methods:
|
||||
isActive:
|
||||
inline_code: |
|
||||
.def_property(
|
||||
"level",
|
||||
[](const AlertInfo& self) { return self.level; },
|
||||
[](AlertInfo& self, wpi::Alert::Level level) { self.level = level; },
|
||||
py::return_value_policy::copy,
|
||||
py::doc("The level of the alert (HIGH, MEDIUM, or LOW).")
|
||||
);
|
||||
@@ -4,6 +4,7 @@ from . import _init__simulation
|
||||
from ._simulation import (
|
||||
ADXL345Sim,
|
||||
AddressableLEDSim,
|
||||
AlertSim,
|
||||
AnalogEncoderSim,
|
||||
AnalogInputSim,
|
||||
BatterySim,
|
||||
|
||||
@@ -1,250 +0,0 @@
|
||||
// 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/util/Alert.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "wpi/nt/NetworkTableInstance.hpp"
|
||||
#include "wpi/nt/StringArrayTopic.hpp"
|
||||
#include "wpi/simulation/SimHooks.hpp"
|
||||
#include "wpi/smartdashboard/SmartDashboard.hpp"
|
||||
|
||||
using namespace wpi;
|
||||
using enum Alert::AlertType;
|
||||
class AlertsTest : public ::testing::Test {
|
||||
public:
|
||||
~AlertsTest() override {
|
||||
// test all destructors
|
||||
Update();
|
||||
EXPECT_EQ(GetSubscriberForType(kError).Get().size(), 0ul);
|
||||
EXPECT_EQ(GetSubscriberForType(kWarning).Get().size(), 0ul);
|
||||
EXPECT_EQ(GetSubscriberForType(kInfo).Get().size(), 0ul);
|
||||
}
|
||||
|
||||
std::string GetGroupName() {
|
||||
const ::testing::TestInfo* testInfo =
|
||||
::testing::UnitTest::GetInstance()->current_test_info();
|
||||
return fmt::format("{}_{}", testInfo->test_suite_name(), testInfo->name());
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
Alert MakeAlert(Args&&... args) {
|
||||
return Alert(GetGroupName(), std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
std::vector<std::string> GetActiveAlerts(Alert::AlertType type) {
|
||||
Update();
|
||||
return GetSubscriberForType(type).Get();
|
||||
}
|
||||
|
||||
bool IsAlertActive(std::string_view text, Alert::AlertType type) {
|
||||
auto activeAlerts = GetActiveAlerts(type);
|
||||
return std::find(activeAlerts.begin(), activeAlerts.end(), text) !=
|
||||
activeAlerts.end();
|
||||
}
|
||||
|
||||
void Update() { wpi::SmartDashboard::UpdateValues(); }
|
||||
|
||||
private:
|
||||
std::string GetSubtableName(Alert::AlertType type) {
|
||||
switch (type) {
|
||||
case kError:
|
||||
return "errors";
|
||||
case kWarning:
|
||||
return "warnings";
|
||||
case kInfo:
|
||||
return "infos";
|
||||
default:
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
const wpi::nt::StringArraySubscriber GetSubscriberForType(
|
||||
Alert::AlertType type) {
|
||||
return wpi::nt::NetworkTableInstance::GetDefault()
|
||||
.GetStringArrayTopic(fmt::format("/SmartDashboard/{}/{}",
|
||||
GetGroupName(), GetSubtableName(type)))
|
||||
.Subscribe({});
|
||||
}
|
||||
};
|
||||
|
||||
#define EXPECT_STATE(type, ...) \
|
||||
EXPECT_EQ(GetActiveAlerts(type), (std::vector<std::string>{__VA_ARGS__}))
|
||||
|
||||
TEST_F(AlertsTest, SetUnsetSingle) {
|
||||
auto one = MakeAlert("one", kInfo);
|
||||
EXPECT_FALSE(IsAlertActive("one", kInfo));
|
||||
one.Set(true);
|
||||
EXPECT_TRUE(IsAlertActive("one", kInfo));
|
||||
one.Set(false);
|
||||
EXPECT_FALSE(IsAlertActive("one", kInfo));
|
||||
}
|
||||
|
||||
TEST_F(AlertsTest, SetUnsetMultiple) {
|
||||
auto one = MakeAlert("one", kError);
|
||||
auto two = MakeAlert("two", kInfo);
|
||||
EXPECT_FALSE(IsAlertActive("one", kError));
|
||||
EXPECT_FALSE(IsAlertActive("two", kInfo));
|
||||
one.Set(true);
|
||||
EXPECT_TRUE(IsAlertActive("one", kError));
|
||||
EXPECT_FALSE(IsAlertActive("two", kInfo));
|
||||
one.Set(true);
|
||||
two.Set(true);
|
||||
EXPECT_TRUE(IsAlertActive("one", kError));
|
||||
EXPECT_TRUE(IsAlertActive("two", kInfo));
|
||||
one.Set(false);
|
||||
EXPECT_FALSE(IsAlertActive("one", kError));
|
||||
EXPECT_TRUE(IsAlertActive("two", kInfo));
|
||||
}
|
||||
|
||||
TEST_F(AlertsTest, SetIsIdempotent) {
|
||||
auto a = MakeAlert("A", kInfo);
|
||||
auto b = MakeAlert("B", kInfo);
|
||||
auto c = MakeAlert("C", kInfo);
|
||||
a.Set(true);
|
||||
|
||||
b.Set(true);
|
||||
c.Set(true);
|
||||
|
||||
const auto startState = GetActiveAlerts(kInfo);
|
||||
|
||||
b.Set(true);
|
||||
EXPECT_STATE(kInfo, startState);
|
||||
|
||||
a.Set(true);
|
||||
EXPECT_STATE(kInfo, startState);
|
||||
}
|
||||
|
||||
TEST_F(AlertsTest, DestructorUnsetsAlert) {
|
||||
{
|
||||
auto alert = MakeAlert("alert", kWarning);
|
||||
alert.Set(true);
|
||||
EXPECT_TRUE(IsAlertActive("alert", kWarning));
|
||||
}
|
||||
EXPECT_FALSE(IsAlertActive("alert", kWarning));
|
||||
}
|
||||
|
||||
TEST_F(AlertsTest, SetTextWhileUnset) {
|
||||
auto alert = MakeAlert("BEFORE", kInfo);
|
||||
EXPECT_EQ("BEFORE", alert.GetText());
|
||||
alert.Set(true);
|
||||
EXPECT_TRUE(IsAlertActive("BEFORE", kInfo));
|
||||
alert.Set(false);
|
||||
EXPECT_FALSE(IsAlertActive("BEFORE", kInfo));
|
||||
alert.SetText("AFTER");
|
||||
EXPECT_EQ("AFTER", alert.GetText());
|
||||
alert.Set(true);
|
||||
EXPECT_FALSE(IsAlertActive("BEFORE", kInfo));
|
||||
EXPECT_TRUE(IsAlertActive("AFTER", kInfo));
|
||||
}
|
||||
|
||||
TEST_F(AlertsTest, SetTextWhileSet) {
|
||||
auto alert = MakeAlert("BEFORE", kInfo);
|
||||
EXPECT_EQ("BEFORE", alert.GetText());
|
||||
alert.Set(true);
|
||||
EXPECT_TRUE(IsAlertActive("BEFORE", kInfo));
|
||||
alert.SetText("AFTER");
|
||||
EXPECT_EQ("AFTER", alert.GetText());
|
||||
EXPECT_FALSE(IsAlertActive("BEFORE", kInfo));
|
||||
EXPECT_TRUE(IsAlertActive("AFTER", kInfo));
|
||||
}
|
||||
|
||||
TEST_F(AlertsTest, SetTextDoesNotAffectFirstOrderSort) {
|
||||
wpi::sim::PauseTiming();
|
||||
|
||||
auto a = MakeAlert("A", kError);
|
||||
auto b = MakeAlert("B", kError);
|
||||
auto c = MakeAlert("C", kError);
|
||||
|
||||
a.Set(true);
|
||||
wpi::sim::StepTiming(1_s);
|
||||
b.Set(true);
|
||||
wpi::sim::StepTiming(1_s);
|
||||
c.Set(true);
|
||||
|
||||
auto expectedEndState = GetActiveAlerts(kError);
|
||||
std::replace(expectedEndState.begin(), expectedEndState.end(),
|
||||
std::string("B"), std::string("AFTER"));
|
||||
b.SetText("AFTER");
|
||||
|
||||
EXPECT_STATE(kError, expectedEndState);
|
||||
wpi::sim::ResumeTiming();
|
||||
}
|
||||
|
||||
TEST_F(AlertsTest, MoveAssign) {
|
||||
auto outer = MakeAlert("outer", kInfo);
|
||||
outer.Set(true);
|
||||
EXPECT_TRUE(IsAlertActive("outer", kInfo));
|
||||
|
||||
{
|
||||
auto inner = MakeAlert("inner", kWarning);
|
||||
inner.Set(true);
|
||||
EXPECT_TRUE(IsAlertActive("inner", kWarning));
|
||||
outer = std::move(inner);
|
||||
// Assignment target should be unset and invalidated as part of move, before
|
||||
// destruction
|
||||
EXPECT_FALSE(IsAlertActive("outer", kInfo));
|
||||
}
|
||||
EXPECT_TRUE(IsAlertActive("inner", kWarning));
|
||||
}
|
||||
|
||||
TEST_F(AlertsTest, MoveConstruct) {
|
||||
auto a = MakeAlert("A", kInfo);
|
||||
a.Set(true);
|
||||
EXPECT_TRUE(IsAlertActive("A", kInfo));
|
||||
Alert b{std::move(a)};
|
||||
EXPECT_TRUE(IsAlertActive("A", kInfo));
|
||||
b.Set(false);
|
||||
EXPECT_FALSE(IsAlertActive("A", kInfo));
|
||||
b.Set(true);
|
||||
EXPECT_TRUE(IsAlertActive("A", kInfo));
|
||||
}
|
||||
|
||||
TEST_F(AlertsTest, SortOrder) {
|
||||
wpi::sim::PauseTiming();
|
||||
auto a = MakeAlert("A", kInfo);
|
||||
auto b = MakeAlert("B", kInfo);
|
||||
auto c = MakeAlert("C", kInfo);
|
||||
a.Set(true);
|
||||
EXPECT_STATE(kInfo, "A");
|
||||
wpi::sim::StepTiming(1_s);
|
||||
b.Set(true);
|
||||
EXPECT_STATE(kInfo, "B", "A");
|
||||
wpi::sim::StepTiming(1_s);
|
||||
c.Set(true);
|
||||
EXPECT_STATE(kInfo, "C", "B", "A");
|
||||
|
||||
wpi::sim::StepTiming(1_s);
|
||||
c.Set(false);
|
||||
EXPECT_STATE(kInfo, "B", "A");
|
||||
|
||||
wpi::sim::StepTiming(1_s);
|
||||
c.Set(true);
|
||||
EXPECT_STATE(kInfo, "C", "B", "A");
|
||||
|
||||
wpi::sim::StepTiming(1_s);
|
||||
a.Set(false);
|
||||
EXPECT_STATE(kInfo, "C", "B");
|
||||
|
||||
wpi::sim::StepTiming(1_s);
|
||||
b.Set(false);
|
||||
EXPECT_STATE(kInfo, "C");
|
||||
|
||||
wpi::sim::StepTiming(1_s);
|
||||
b.Set(true);
|
||||
EXPECT_STATE(kInfo, "B", "C");
|
||||
|
||||
wpi::sim::StepTiming(1_s);
|
||||
a.Set(true);
|
||||
EXPECT_STATE(kInfo, "A", "B", "C");
|
||||
|
||||
wpi::sim::ResumeTiming();
|
||||
}
|
||||
153
wpilibc/src/test/native/cpp/simulation/AlertSimTest.cpp
Normal file
153
wpilibc/src/test/native/cpp/simulation/AlertSimTest.cpp
Normal file
@@ -0,0 +1,153 @@
|
||||
// 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/simulation/AlertSim.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "wpi/driverstation/Alert.hpp"
|
||||
#include "wpi/hal/HALBase.h"
|
||||
|
||||
namespace wpi::sim {
|
||||
|
||||
class AlertSimTest : public ::testing::Test {
|
||||
public:
|
||||
AlertSimTest() { HAL_Initialize(500, 0); }
|
||||
|
||||
~AlertSimTest() override { AlertSim::ResetData(); }
|
||||
|
||||
std::string GetGroupName() {
|
||||
const ::testing::TestInfo* testInfo =
|
||||
::testing::UnitTest::GetInstance()->current_test_info();
|
||||
return fmt::format("{}_{}", testInfo->test_suite_name(), testInfo->name());
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
Alert MakeAlert(Args&&... args) {
|
||||
return Alert(GetGroupName(), std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
std::vector<std::string> GetActiveAlerts(Alert::Level type) {
|
||||
auto alerts = AlertSim::GetAll();
|
||||
std::vector<std::string> activeAlerts;
|
||||
for (const auto& alert : alerts) {
|
||||
if (alert.isActive() && alert.level == type) {
|
||||
activeAlerts.emplace_back(std::move(alert.text));
|
||||
}
|
||||
}
|
||||
return activeAlerts;
|
||||
}
|
||||
|
||||
bool IsAlertActive(std::string_view text, Alert::Level type) {
|
||||
auto alerts = AlertSim::GetAll();
|
||||
return std::any_of(alerts.begin(), alerts.end(),
|
||||
[text, type](const AlertSim::AlertInfo& alert) {
|
||||
return alert.isActive() && alert.level == type &&
|
||||
alert.text == text;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
#define EXPECT_STATE(type, ...) \
|
||||
EXPECT_EQ(GetActiveAlerts(type), (std::vector<std::string>{__VA_ARGS__}))
|
||||
|
||||
TEST_F(AlertSimTest, NoAlertsInitially) {
|
||||
EXPECT_EQ(AlertSim::GetCount(), 0);
|
||||
EXPECT_TRUE(AlertSim::GetAll().empty());
|
||||
}
|
||||
|
||||
TEST_F(AlertSimTest, NoAlertsAfterReset) {
|
||||
auto alert = MakeAlert("alert", Alert::Level::HIGH);
|
||||
alert.Set(true);
|
||||
EXPECT_TRUE(IsAlertActive("alert", Alert::Level::HIGH));
|
||||
AlertSim::ResetData();
|
||||
EXPECT_EQ(AlertSim::GetCount(), 0);
|
||||
EXPECT_TRUE(AlertSim::GetAll().empty());
|
||||
}
|
||||
|
||||
TEST_F(AlertSimTest, SetUnsetSingle) {
|
||||
auto one = MakeAlert("one", Alert::Level::LOW);
|
||||
EXPECT_FALSE(IsAlertActive("one", Alert::Level::LOW));
|
||||
one.Set(true);
|
||||
EXPECT_TRUE(IsAlertActive("one", Alert::Level::LOW));
|
||||
one.Set(false);
|
||||
EXPECT_FALSE(IsAlertActive("one", Alert::Level::LOW));
|
||||
}
|
||||
|
||||
TEST_F(AlertSimTest, SetUnsetMultiple) {
|
||||
auto one = MakeAlert("one", Alert::Level::HIGH);
|
||||
auto two = MakeAlert("two", Alert::Level::LOW);
|
||||
EXPECT_FALSE(IsAlertActive("one", Alert::Level::HIGH));
|
||||
EXPECT_FALSE(IsAlertActive("two", Alert::Level::LOW));
|
||||
one.Set(true);
|
||||
EXPECT_TRUE(IsAlertActive("one", Alert::Level::HIGH));
|
||||
EXPECT_FALSE(IsAlertActive("two", Alert::Level::LOW));
|
||||
one.Set(true);
|
||||
two.Set(true);
|
||||
EXPECT_TRUE(IsAlertActive("one", Alert::Level::HIGH));
|
||||
EXPECT_TRUE(IsAlertActive("two", Alert::Level::LOW));
|
||||
one.Set(false);
|
||||
EXPECT_FALSE(IsAlertActive("one", Alert::Level::HIGH));
|
||||
EXPECT_TRUE(IsAlertActive("two", Alert::Level::LOW));
|
||||
}
|
||||
|
||||
TEST_F(AlertSimTest, SetIsIdempotent) {
|
||||
auto a = MakeAlert("A", Alert::Level::LOW);
|
||||
auto b = MakeAlert("B", Alert::Level::LOW);
|
||||
auto c = MakeAlert("C", Alert::Level::LOW);
|
||||
a.Set(true);
|
||||
|
||||
b.Set(true);
|
||||
c.Set(true);
|
||||
|
||||
const auto startState = GetActiveAlerts(Alert::Level::LOW);
|
||||
|
||||
b.Set(true);
|
||||
EXPECT_STATE(Alert::Level::LOW, startState);
|
||||
|
||||
a.Set(true);
|
||||
EXPECT_STATE(Alert::Level::LOW, startState);
|
||||
}
|
||||
|
||||
TEST_F(AlertSimTest, DestructorUnsetsAlert) {
|
||||
{
|
||||
auto alert = MakeAlert("alert", Alert::Level::MEDIUM);
|
||||
alert.Set(true);
|
||||
EXPECT_TRUE(IsAlertActive("alert", Alert::Level::MEDIUM));
|
||||
}
|
||||
EXPECT_FALSE(IsAlertActive("alert", Alert::Level::MEDIUM));
|
||||
}
|
||||
|
||||
TEST_F(AlertSimTest, SetTextWhileUnset) {
|
||||
auto alert = MakeAlert("BEFORE", Alert::Level::LOW);
|
||||
EXPECT_EQ("BEFORE", alert.GetText());
|
||||
alert.Set(true);
|
||||
EXPECT_TRUE(IsAlertActive("BEFORE", Alert::Level::LOW));
|
||||
alert.Set(false);
|
||||
EXPECT_FALSE(IsAlertActive("BEFORE", Alert::Level::LOW));
|
||||
alert.SetText("AFTER");
|
||||
EXPECT_EQ("AFTER", alert.GetText());
|
||||
alert.Set(true);
|
||||
EXPECT_FALSE(IsAlertActive("BEFORE", Alert::Level::LOW));
|
||||
EXPECT_TRUE(IsAlertActive("AFTER", Alert::Level::LOW));
|
||||
}
|
||||
|
||||
TEST_F(AlertSimTest, SetTextWhileSet) {
|
||||
auto alert = MakeAlert("BEFORE", Alert::Level::LOW);
|
||||
EXPECT_EQ("BEFORE", alert.GetText());
|
||||
alert.Set(true);
|
||||
EXPECT_TRUE(IsAlertActive("BEFORE", Alert::Level::LOW));
|
||||
alert.SetText("AFTER");
|
||||
EXPECT_EQ("AFTER", alert.GetText());
|
||||
EXPECT_FALSE(IsAlertActive("BEFORE", Alert::Level::LOW));
|
||||
EXPECT_TRUE(IsAlertActive("AFTER", Alert::Level::LOW));
|
||||
}
|
||||
|
||||
} // namespace wpi::sim
|
||||
@@ -2,222 +2,140 @@ import typing as T
|
||||
|
||||
import pytest
|
||||
|
||||
from ntcore import NetworkTableInstance
|
||||
from wpilib import Alert, SmartDashboard
|
||||
from wpilib.simulation import pauseTiming, resumeTiming, stepTiming
|
||||
from wpilib import Alert
|
||||
from wpilib.simulation import AlertSim
|
||||
|
||||
AlertType = Alert.AlertType
|
||||
Level = Alert.Level
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def group_name(nt, request):
|
||||
def group_name(request):
|
||||
|
||||
group_name = f"AlertTest_{request.node.name}"
|
||||
yield group_name
|
||||
|
||||
SmartDashboard.updateValues()
|
||||
assert len(get_active_alerts(nt, group_name, AlertType.kError)) == 0
|
||||
assert len(get_active_alerts(nt, group_name, AlertType.kWarning)) == 0
|
||||
assert len(get_active_alerts(nt, group_name, AlertType.kInfo)) == 0
|
||||
|
||||
|
||||
def get_subscriber_for_type(
|
||||
nt: NetworkTableInstance, group_name: str, alert_type: AlertType
|
||||
):
|
||||
subtable_name = {
|
||||
AlertType.kError: "errors",
|
||||
AlertType.kWarning: "warnings",
|
||||
AlertType.kInfo: "infos",
|
||||
}.get(alert_type, "unknown")
|
||||
topic = f"/SmartDashboard/{group_name}/{subtable_name}"
|
||||
return nt.getStringArrayTopic(topic).subscribe([])
|
||||
AlertSim.resetData()
|
||||
|
||||
|
||||
def get_active_alerts(
|
||||
nt: NetworkTableInstance, group_name: str, alert_type: AlertType
|
||||
group_name: str, level: Alert.Level
|
||||
) -> T.List[str]:
|
||||
SmartDashboard.updateValues()
|
||||
with get_subscriber_for_type(nt, group_name, alert_type) as sub:
|
||||
return sub.get()
|
||||
return [
|
||||
a.text
|
||||
for a in AlertSim.getAll()
|
||||
if a.group == group_name and a.level == level and a.isActive()
|
||||
]
|
||||
|
||||
|
||||
def is_alert_active(
|
||||
nt: NetworkTableInstance, group_name: str, text: str, alert_type: AlertType
|
||||
group_name: str, text: str, level: Alert.Level
|
||||
):
|
||||
active_alerts = get_active_alerts(nt, group_name, alert_type)
|
||||
return text in active_alerts
|
||||
matches = [
|
||||
a
|
||||
for a in AlertSim.getAll()
|
||||
if a.group == group_name and a.level == level and a.text == text and a.isActive()
|
||||
]
|
||||
return len(matches) > 0
|
||||
|
||||
|
||||
def assert_state(
|
||||
nt: NetworkTableInstance,
|
||||
group_name: str,
|
||||
alert_type: AlertType,
|
||||
level: Alert.Level,
|
||||
expected_state: T.List[str],
|
||||
):
|
||||
assert expected_state == get_active_alerts(nt, group_name, alert_type)
|
||||
assert expected_state == get_active_alerts(group_name, level)
|
||||
|
||||
|
||||
def test_set_unset_single(nt, group_name):
|
||||
with Alert(group_name, "one", AlertType.kError) as one:
|
||||
def test_set_unset_single(group_name):
|
||||
with Alert(group_name, "one", Alert.Level.HIGH) as one:
|
||||
|
||||
assert not is_alert_active(nt, group_name, "one", AlertType.kError)
|
||||
assert not is_alert_active(nt, group_name, "two", AlertType.kInfo)
|
||||
assert not is_alert_active(group_name, "one", Alert.Level.HIGH)
|
||||
assert not is_alert_active(group_name, "two", Alert.Level.LOW)
|
||||
|
||||
one.set(True)
|
||||
assert is_alert_active(nt, group_name, "one", AlertType.kError)
|
||||
assert is_alert_active(group_name, "one", Alert.Level.HIGH)
|
||||
|
||||
one.set(True)
|
||||
assert is_alert_active(nt, group_name, "one", AlertType.kError)
|
||||
assert is_alert_active(group_name, "one", Alert.Level.HIGH)
|
||||
|
||||
one.set(False)
|
||||
assert not is_alert_active(nt, group_name, "one", AlertType.kError)
|
||||
assert not is_alert_active(group_name, "one", Alert.Level.HIGH)
|
||||
|
||||
|
||||
def test_set_unset_multiple(nt, group_name):
|
||||
def test_set_unset_multiple(group_name):
|
||||
with (
|
||||
Alert(group_name, "one", AlertType.kError) as one,
|
||||
Alert(group_name, "two", AlertType.kInfo) as two,
|
||||
Alert(group_name, "one", Alert.Level.HIGH) as one,
|
||||
Alert(group_name, "two", Alert.Level.LOW) as two,
|
||||
):
|
||||
|
||||
assert not is_alert_active(nt, group_name, "one", AlertType.kError)
|
||||
assert not is_alert_active(nt, group_name, "two", AlertType.kInfo)
|
||||
assert not is_alert_active(group_name, "one", Alert.Level.HIGH)
|
||||
assert not is_alert_active(group_name, "two", Alert.Level.LOW)
|
||||
|
||||
one.set(True)
|
||||
assert is_alert_active(nt, group_name, "one", AlertType.kError)
|
||||
assert not is_alert_active(nt, group_name, "two", AlertType.kInfo)
|
||||
assert is_alert_active(group_name, "one", Alert.Level.HIGH)
|
||||
assert not is_alert_active(group_name, "two", Alert.Level.LOW)
|
||||
|
||||
one.set(True)
|
||||
two.set(True)
|
||||
assert is_alert_active(nt, group_name, "one", AlertType.kError)
|
||||
assert is_alert_active(nt, group_name, "two", AlertType.kInfo)
|
||||
assert is_alert_active(group_name, "one", Alert.Level.HIGH)
|
||||
assert is_alert_active(group_name, "two", Alert.Level.LOW)
|
||||
|
||||
one.set(False)
|
||||
assert not is_alert_active(nt, group_name, "one", AlertType.kError)
|
||||
assert is_alert_active(nt, group_name, "two", AlertType.kInfo)
|
||||
assert not is_alert_active(group_name, "one", Alert.Level.HIGH)
|
||||
assert is_alert_active(group_name, "two", Alert.Level.LOW)
|
||||
|
||||
|
||||
def test_set_is_idempotent(nt, group_name):
|
||||
def test_set_is_idempotent(group_name):
|
||||
group_name = group_name
|
||||
with (
|
||||
Alert(group_name, "A", AlertType.kInfo) as a,
|
||||
Alert(group_name, "B", AlertType.kInfo) as b,
|
||||
Alert(group_name, "C", AlertType.kInfo) as c,
|
||||
Alert(group_name, "A", Alert.Level.LOW) as a,
|
||||
Alert(group_name, "B", Alert.Level.LOW) as b,
|
||||
Alert(group_name, "C", Alert.Level.LOW) as c,
|
||||
):
|
||||
|
||||
a.set(True)
|
||||
b.set(True)
|
||||
c.set(True)
|
||||
|
||||
start_state = get_active_alerts(nt, group_name, AlertType.kInfo)
|
||||
start_state = get_active_alerts(group_name, Alert.Level.LOW)
|
||||
assert set(start_state) == {"A", "B", "C"}
|
||||
|
||||
b.set(True)
|
||||
assert_state(nt, group_name, AlertType.kInfo, start_state)
|
||||
assert_state(group_name, Alert.Level.LOW, start_state)
|
||||
|
||||
a.set(True)
|
||||
assert_state(nt, group_name, AlertType.kInfo, start_state)
|
||||
assert_state(group_name, Alert.Level.LOW, start_state)
|
||||
|
||||
|
||||
def test_close_unsets_alert(nt, group_name):
|
||||
def test_close_unsets_alert(group_name):
|
||||
group_name = group_name
|
||||
with Alert(group_name, "alert", AlertType.kWarning) as alert:
|
||||
with Alert(group_name, "alert", Alert.Level.MEDIUM) as alert:
|
||||
alert.set(True)
|
||||
assert is_alert_active(nt, group_name, "alert", AlertType.kWarning)
|
||||
assert not is_alert_active(nt, group_name, "alert", AlertType.kWarning)
|
||||
assert is_alert_active(group_name, "alert", Alert.Level.MEDIUM)
|
||||
assert not is_alert_active(group_name, "alert", Alert.Level.MEDIUM)
|
||||
|
||||
|
||||
def test_set_text_while_unset(nt, group_name):
|
||||
def test_set_text_while_unset(group_name):
|
||||
group_name = group_name
|
||||
with Alert(group_name, "BEFORE", AlertType.kInfo) as alert:
|
||||
with Alert(group_name, "BEFORE", Alert.Level.LOW) as alert:
|
||||
assert alert.getText() == "BEFORE"
|
||||
alert.set(True)
|
||||
assert is_alert_active(nt, group_name, "BEFORE", AlertType.kInfo)
|
||||
assert is_alert_active(group_name, "BEFORE", Alert.Level.LOW)
|
||||
alert.set(False)
|
||||
assert not is_alert_active(nt, group_name, "BEFORE", AlertType.kInfo)
|
||||
assert not is_alert_active(group_name, "BEFORE", Alert.Level.LOW)
|
||||
alert.setText("AFTER")
|
||||
assert alert.getText() == "AFTER"
|
||||
alert.set(True)
|
||||
assert not is_alert_active(nt, group_name, "BEFORE", AlertType.kInfo)
|
||||
assert is_alert_active(nt, group_name, "AFTER", AlertType.kInfo)
|
||||
assert not is_alert_active(group_name, "BEFORE", Alert.Level.LOW)
|
||||
assert is_alert_active(group_name, "AFTER", Alert.Level.LOW)
|
||||
|
||||
|
||||
def test_set_text_while_set(nt, group_name):
|
||||
with Alert(group_name, "BEFORE", AlertType.kInfo) as alert:
|
||||
def test_set_text_while_set(group_name):
|
||||
with Alert(group_name, "BEFORE", Alert.Level.LOW) as alert:
|
||||
assert alert.getText() == "BEFORE"
|
||||
alert.set(True)
|
||||
assert is_alert_active(nt, group_name, "BEFORE", AlertType.kInfo)
|
||||
assert is_alert_active(group_name, "BEFORE", Alert.Level.LOW)
|
||||
alert.setText("AFTER")
|
||||
assert alert.getText() == "AFTER"
|
||||
assert not is_alert_active(nt, group_name, "BEFORE", AlertType.kInfo)
|
||||
assert is_alert_active(nt, group_name, "AFTER", AlertType.kInfo)
|
||||
|
||||
|
||||
def test_set_text_does_not_affect_sort(nt, group_name):
|
||||
pauseTiming()
|
||||
try:
|
||||
with (
|
||||
Alert(group_name, "A", AlertType.kInfo) as a,
|
||||
Alert(group_name, "B", AlertType.kInfo) as b,
|
||||
Alert(group_name, "C", AlertType.kInfo) as c,
|
||||
):
|
||||
|
||||
a.set(True)
|
||||
stepTiming(1)
|
||||
b.set(True)
|
||||
stepTiming(1)
|
||||
c.set(True)
|
||||
|
||||
expected_state = get_active_alerts(nt, group_name, AlertType.kInfo)
|
||||
expected_state[expected_state.index("B")] = "AFTER"
|
||||
|
||||
b.setText("AFTER")
|
||||
assert_state(nt, group_name, AlertType.kInfo, expected_state)
|
||||
finally:
|
||||
resumeTiming()
|
||||
|
||||
|
||||
def test_sort_order(nt, group_name):
|
||||
pauseTiming()
|
||||
try:
|
||||
with (
|
||||
Alert(group_name, "A", AlertType.kInfo) as a,
|
||||
Alert(group_name, "B", AlertType.kInfo) as b,
|
||||
Alert(group_name, "C", AlertType.kInfo) as c,
|
||||
):
|
||||
|
||||
a.set(True)
|
||||
assert_state(nt, group_name, AlertType.kInfo, ["A"])
|
||||
|
||||
stepTiming(1)
|
||||
b.set(True)
|
||||
assert_state(nt, group_name, AlertType.kInfo, ["B", "A"])
|
||||
|
||||
stepTiming(1)
|
||||
c.set(True)
|
||||
assert_state(nt, group_name, AlertType.kInfo, ["C", "B", "A"])
|
||||
|
||||
stepTiming(1)
|
||||
c.set(False)
|
||||
assert_state(nt, group_name, AlertType.kInfo, ["B", "A"])
|
||||
|
||||
stepTiming(1)
|
||||
c.set(True)
|
||||
assert_state(nt, group_name, AlertType.kInfo, ["C", "B", "A"])
|
||||
|
||||
stepTiming(1)
|
||||
a.set(False)
|
||||
assert_state(nt, group_name, AlertType.kInfo, ["C", "B"])
|
||||
|
||||
stepTiming(1)
|
||||
b.set(False)
|
||||
assert_state(nt, group_name, AlertType.kInfo, ["C"])
|
||||
|
||||
stepTiming(1)
|
||||
b.set(True)
|
||||
assert_state(nt, group_name, AlertType.kInfo, ["B", "C"])
|
||||
|
||||
stepTiming(1)
|
||||
a.set(True)
|
||||
assert_state(nt, group_name, AlertType.kInfo, ["A", "B", "C"])
|
||||
finally:
|
||||
resumeTiming()
|
||||
assert not is_alert_active(group_name, "BEFORE", Alert.Level.LOW)
|
||||
assert is_alert_active(group_name, "AFTER", Alert.Level.LOW)
|
||||
|
||||
155
wpilibj/src/main/java/org/wpilib/driverstation/Alert.java
Normal file
155
wpilibj/src/main/java/org/wpilib/driverstation/Alert.java
Normal file
@@ -0,0 +1,155 @@
|
||||
// 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 org.wpilib.hardware.hal.AlertJNI;
|
||||
|
||||
/**
|
||||
* Persistent alert to be sent via NetworkTables. Alerts are tagged with a type of {@code ERROR},
|
||||
* {@code WARNING}, or {@code INFO} to denote urgency. See {@link
|
||||
* org.wpilib.driverstation.Alert.Level Level} 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, with newly activated alerts first.
|
||||
*
|
||||
* <p>Alerts should be created once and stored persistently, then updated to "active" or "inactive"
|
||||
* as necessary. {@link #set(boolean)} can be safely called periodically.
|
||||
*
|
||||
* <pre>
|
||||
* class Robot {
|
||||
* Alert alert = new Alert("Something went wrong", Alert.Level.WARNING);
|
||||
*
|
||||
* periodic() {
|
||||
* alert.set(...);
|
||||
* }
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* <p>Alternatively, alerts which are only used once at startup can be created and activated inline.
|
||||
*
|
||||
* <pre>
|
||||
* public Robot() {
|
||||
* new Alert("Failed to load auto paths", Alert.Level.ERROR).set(true);
|
||||
* }
|
||||
* </pre>
|
||||
*/
|
||||
public class Alert implements AutoCloseable {
|
||||
/** Represents an alert's level of urgency. */
|
||||
public enum Level {
|
||||
/**
|
||||
* High priority alert - displayed first with a red "X" symbol. Use this type for problems which
|
||||
* will seriously affect the robot's functionality and thus require immediate attention.
|
||||
*/
|
||||
HIGH(AlertJNI.LEVEL_HIGH),
|
||||
|
||||
/** Alternate name for a high priority alert. */
|
||||
ERROR(HIGH.m_value),
|
||||
|
||||
/**
|
||||
* Medium priority alert - displayed second with a yellow "!" symbol. Use this type for problems
|
||||
* which could affect the robot's functionality but do not necessarily require immediate
|
||||
* attention.
|
||||
*/
|
||||
MEDIUM(AlertJNI.LEVEL_MEDIUM),
|
||||
|
||||
/** Alternate name for a medium priority alert. */
|
||||
WARNING(MEDIUM.m_value),
|
||||
|
||||
/**
|
||||
* Low priority alert - displayed last 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.
|
||||
*/
|
||||
LOW(AlertJNI.LEVEL_LOW),
|
||||
|
||||
/** Alternate name for a low priority alert. */
|
||||
INFO(LOW.m_value);
|
||||
|
||||
private final int m_value;
|
||||
|
||||
Level(int value) {
|
||||
m_value = value;
|
||||
}
|
||||
}
|
||||
|
||||
private final Level m_type;
|
||||
private int m_handle;
|
||||
|
||||
/**
|
||||
* 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, Level 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, Level type) {
|
||||
m_type = type;
|
||||
m_handle = AlertJNI.createAlert(group, text, type.m_value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether the alert should currently be displayed. This method can be safely called
|
||||
* periodically.
|
||||
*
|
||||
* @param active Whether to display the alert.
|
||||
*/
|
||||
public void set(boolean active) {
|
||||
AlertJNI.setAlertActive(m_handle, active);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets whether the alert is active.
|
||||
*
|
||||
* @return whether the alert is active.
|
||||
*/
|
||||
public boolean get() {
|
||||
return AlertJNI.isAlertActive(m_handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
AlertJNI.setAlertText(m_handle, text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current alert text.
|
||||
*
|
||||
* @return the current text.
|
||||
*/
|
||||
public String getText() {
|
||||
return AlertJNI.getAlertText(m_handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the type of this alert.
|
||||
*
|
||||
* @return the type
|
||||
*/
|
||||
public Level getType() {
|
||||
return m_type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
AlertJNI.destroyAlert(m_handle);
|
||||
m_handle = 0;
|
||||
}
|
||||
}
|
||||
91
wpilibj/src/main/java/org/wpilib/simulation/AlertSim.java
Normal file
91
wpilibj/src/main/java/org/wpilib/simulation/AlertSim.java
Normal file
@@ -0,0 +1,91 @@
|
||||
// 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.simulation;
|
||||
|
||||
import org.wpilib.driverstation.Alert.Level;
|
||||
import org.wpilib.hardware.hal.AlertJNI;
|
||||
import org.wpilib.hardware.hal.simulation.AlertDataJNI;
|
||||
|
||||
/** Simulation for alerts. */
|
||||
public final class AlertSim {
|
||||
private AlertSim() {
|
||||
throw new UnsupportedOperationException("This is a utility class!");
|
||||
}
|
||||
|
||||
/** Information about an alert. */
|
||||
public static class AlertInfo {
|
||||
AlertInfo(AlertDataJNI.AlertInfo info) {
|
||||
this.handle = info.handle;
|
||||
this.group = info.group;
|
||||
this.text = info.text;
|
||||
this.activeStartTime = info.activeStartTime;
|
||||
this.level =
|
||||
switch (info.level) {
|
||||
case AlertJNI.LEVEL_HIGH -> Level.HIGH;
|
||||
case AlertJNI.LEVEL_MEDIUM -> Level.MEDIUM;
|
||||
case AlertJNI.LEVEL_LOW -> Level.LOW;
|
||||
default -> throw new IllegalArgumentException("Unknown alert level: " + info.level);
|
||||
};
|
||||
}
|
||||
|
||||
/** The handle of the alert. */
|
||||
@SuppressWarnings("MemberName")
|
||||
public final int handle;
|
||||
|
||||
/** The group of the alert. */
|
||||
@SuppressWarnings("MemberName")
|
||||
public final String group;
|
||||
|
||||
/** The text of the alert. */
|
||||
@SuppressWarnings("MemberName")
|
||||
public final String text;
|
||||
|
||||
/** The time the alert became active. 0 if not active. */
|
||||
@SuppressWarnings("MemberName")
|
||||
public final long activeStartTime;
|
||||
|
||||
/** The level of the alert (HIGH, MEDIUM, or LOW). */
|
||||
@SuppressWarnings("MemberName")
|
||||
public final Level level;
|
||||
|
||||
/**
|
||||
* Returns whether the alert is currently active.
|
||||
*
|
||||
* @return true if the alert is active, false otherwise
|
||||
*/
|
||||
public boolean isActive() {
|
||||
return activeStartTime != 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the number of alerts. Note: this is not guaranteed to be consistent with the number of
|
||||
* alerts returned by GetAll.
|
||||
*
|
||||
* @return the number of alerts
|
||||
*/
|
||||
public static int getCount() {
|
||||
return AlertDataJNI.getNumAlerts();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets detailed information about each alert.
|
||||
*
|
||||
* @return Alerts
|
||||
*/
|
||||
public static AlertInfo[] getAll() {
|
||||
AlertDataJNI.AlertInfo[] alertInfos = AlertDataJNI.getAlerts();
|
||||
AlertInfo[] infos = new AlertInfo[alertInfos.length];
|
||||
for (int i = 0; i < alertInfos.length; i++) {
|
||||
infos[i] = new AlertInfo(alertInfos[i]);
|
||||
}
|
||||
return infos;
|
||||
}
|
||||
|
||||
/** Resets all alert simulation data. */
|
||||
public static void resetData() {
|
||||
AlertDataJNI.resetData();
|
||||
}
|
||||
}
|
||||
@@ -1,232 +0,0 @@
|
||||
// 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.util;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.EnumMap;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
import org.wpilib.smartdashboard.SmartDashboard;
|
||||
import org.wpilib.system.RobotController;
|
||||
import org.wpilib.util.sendable.Sendable;
|
||||
import org.wpilib.util.sendable.SendableBuilder;
|
||||
|
||||
/**
|
||||
* 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 org.wpilib.util.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, with newly activated
|
||||
* alerts first.
|
||||
*
|
||||
* <p>Alerts should be created once and stored persistently, then updated to "active" or "inactive"
|
||||
* as necessary. {@link #set(boolean)} can be safely called periodically.
|
||||
*
|
||||
* <p><b>This API is new for 2025, but is likely to change in future seasons to facilitate deeper
|
||||
* integration with the robot control system.</b>
|
||||
*
|
||||
* <pre>
|
||||
* class Robot {
|
||||
* Alert alert = new Alert("Something went wrong", AlertType.kWarning);
|
||||
*
|
||||
* periodic() {
|
||||
* alert.set(...);
|
||||
* }
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* <p>Alternatively, alerts which are only used once at startup can be created and activated inline.
|
||||
*
|
||||
* <pre>
|
||||
* public Robot() {
|
||||
* new Alert("Failed to load auto paths", AlertType.kError).set(true);
|
||||
* }
|
||||
* </pre>
|
||||
*/
|
||||
public class Alert implements AutoCloseable {
|
||||
/** 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
|
||||
}
|
||||
|
||||
private final AlertType m_type;
|
||||
private boolean m_active;
|
||||
private long m_activeStartTime;
|
||||
private String m_text;
|
||||
private Set<PublishedAlert> m_activeAlerts;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
@SuppressWarnings("this-escape")
|
||||
public Alert(String group, String text, AlertType type) {
|
||||
m_type = type;
|
||||
m_text = text;
|
||||
m_activeAlerts = SendableAlerts.forGroup(group).getActiveAlertsStorage(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether the alert should currently be displayed. This method can be safely called
|
||||
* periodically.
|
||||
*
|
||||
* @param active Whether to display the alert.
|
||||
*/
|
||||
public void set(boolean active) {
|
||||
if (active == m_active) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (active) {
|
||||
m_activeStartTime = RobotController.getTime();
|
||||
m_activeAlerts.add(new PublishedAlert(m_activeStartTime, m_text));
|
||||
} else {
|
||||
m_activeAlerts.remove(new PublishedAlert(m_activeStartTime, m_text));
|
||||
}
|
||||
m_active = active;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets whether the alert is active.
|
||||
*
|
||||
* @return whether the alert is active.
|
||||
*/
|
||||
public boolean get() {
|
||||
return m_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) {
|
||||
if (text.equals(m_text)) {
|
||||
return;
|
||||
}
|
||||
var oldText = m_text;
|
||||
m_text = text;
|
||||
if (m_active) {
|
||||
m_activeAlerts.remove(new PublishedAlert(m_activeStartTime, oldText));
|
||||
m_activeAlerts.add(new PublishedAlert(m_activeStartTime, m_text));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current alert text.
|
||||
*
|
||||
* @return the current text.
|
||||
*/
|
||||
public String getText() {
|
||||
return m_text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the type of this alert.
|
||||
*
|
||||
* @return the type
|
||||
*/
|
||||
public AlertType getType() {
|
||||
return m_type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
set(false);
|
||||
}
|
||||
|
||||
private record PublishedAlert(long timestamp, String text) implements Comparable<PublishedAlert> {
|
||||
private static final Comparator<PublishedAlert> comparator =
|
||||
Comparator.comparingLong((PublishedAlert alert) -> alert.timestamp())
|
||||
.reversed()
|
||||
.thenComparing(Comparator.comparing((PublishedAlert alert) -> alert.text()));
|
||||
|
||||
@Override
|
||||
public int compareTo(PublishedAlert o) {
|
||||
return comparator.compare(this, o);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class SendableAlerts implements Sendable {
|
||||
private static final Map<String, SendableAlerts> groups = new HashMap<String, SendableAlerts>();
|
||||
|
||||
private final EnumMap<AlertType, Set<PublishedAlert>> m_alerts = new EnumMap<>(AlertType.class);
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
public Set<PublishedAlert> getActiveAlertsStorage(AlertType type) {
|
||||
return m_alerts.computeIfAbsent(type, _type -> new TreeSet<>());
|
||||
}
|
||||
|
||||
private String[] getStrings(AlertType type) {
|
||||
return getActiveAlertsStorage(type).stream().map(a -> a.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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
private static SendableAlerts forGroup(String group) {
|
||||
return groups.computeIfAbsent(
|
||||
group,
|
||||
_group -> {
|
||||
var sendable = new SendableAlerts();
|
||||
SmartDashboard.putData(_group, sendable);
|
||||
return sendable;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
158
wpilibj/src/test/java/org/wpilib/simulation/AlertSimTest.java
Normal file
158
wpilibj/src/test/java/org/wpilib/simulation/AlertSimTest.java
Normal file
@@ -0,0 +1,158 @@
|
||||
// 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.simulation;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.TestInfo;
|
||||
import org.wpilib.driverstation.Alert;
|
||||
import org.wpilib.driverstation.Alert.Level;
|
||||
import org.wpilib.hardware.hal.HAL;
|
||||
|
||||
class AlertSimTest {
|
||||
private String m_groupName;
|
||||
|
||||
@BeforeEach
|
||||
void setup(TestInfo info) {
|
||||
HAL.initialize(500, 0);
|
||||
m_groupName = "AlertTest_" + info.getDisplayName();
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void cleanup() {
|
||||
AlertSim.resetData();
|
||||
}
|
||||
|
||||
private String[] getActiveAlerts(Level type) {
|
||||
return Arrays.stream(AlertSim.getAll())
|
||||
.filter(a -> a.isActive() && a.level == type)
|
||||
.map(a -> a.text)
|
||||
.toArray(String[]::new);
|
||||
}
|
||||
|
||||
private boolean isAlertActive(String text, Alert.Level type) {
|
||||
return Arrays.stream(AlertSim.getAll())
|
||||
.filter(a -> a.isActive() && a.level == type)
|
||||
.anyMatch(a -> a.text.equals(text));
|
||||
}
|
||||
|
||||
private void assertState(Alert.Level type, List<String> state) {
|
||||
assertEquals(state, Arrays.asList(getActiveAlerts(type)));
|
||||
}
|
||||
|
||||
private Alert makeAlert(String text, Alert.Level type) {
|
||||
return new Alert(m_groupName, text, type);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testInitialization() {
|
||||
assertEquals(0, AlertSim.getCount());
|
||||
assertEquals(0, AlertSim.getAll().length);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testReset() {
|
||||
try (var alert = makeAlert("alert", Level.HIGH)) {
|
||||
alert.set(true);
|
||||
assertTrue(isAlertActive("alert", Level.HIGH));
|
||||
}
|
||||
AlertSim.resetData();
|
||||
assertFalse(isAlertActive("alert", Level.HIGH));
|
||||
}
|
||||
|
||||
@Test
|
||||
void setUnsetSingle() {
|
||||
try (var one = makeAlert("one", Level.LOW)) {
|
||||
assertFalse(isAlertActive("one", Level.LOW));
|
||||
one.set(true);
|
||||
assertTrue(isAlertActive("one", Level.LOW));
|
||||
one.set(false);
|
||||
assertFalse(isAlertActive("one", Level.LOW));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void setUnsetMultiple() {
|
||||
try (var one = makeAlert("one", Level.HIGH);
|
||||
var two = makeAlert("two", Level.LOW)) {
|
||||
assertFalse(isAlertActive("one", Level.HIGH));
|
||||
assertFalse(isAlertActive("two", Level.LOW));
|
||||
one.set(true);
|
||||
assertTrue(isAlertActive("one", Level.HIGH));
|
||||
assertFalse(isAlertActive("two", Level.LOW));
|
||||
one.set(true);
|
||||
two.set(true);
|
||||
assertTrue(isAlertActive("one", Level.HIGH));
|
||||
assertTrue(isAlertActive("two", Level.LOW));
|
||||
one.set(false);
|
||||
assertFalse(isAlertActive("one", Level.HIGH));
|
||||
assertTrue(isAlertActive("two", Level.LOW));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void setIsIdempotent() {
|
||||
try (var a = makeAlert("A", Level.LOW);
|
||||
var b = makeAlert("B", Level.LOW);
|
||||
var c = makeAlert("C", Level.LOW)) {
|
||||
a.set(true);
|
||||
b.set(true);
|
||||
c.set(true);
|
||||
|
||||
var startState = List.of(getActiveAlerts(Level.LOW));
|
||||
|
||||
b.set(true);
|
||||
assertState(Level.LOW, startState);
|
||||
|
||||
a.set(true);
|
||||
assertState(Level.LOW, startState);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void closeUnsetsAlert() {
|
||||
try (var alert = makeAlert("alert", Level.MEDIUM)) {
|
||||
alert.set(true);
|
||||
assertTrue(isAlertActive("alert", Level.MEDIUM));
|
||||
}
|
||||
assertFalse(isAlertActive("alert", Level.MEDIUM));
|
||||
}
|
||||
|
||||
@Test
|
||||
void setTextWhileUnset() {
|
||||
try (var alert = makeAlert("BEFORE", Level.LOW)) {
|
||||
assertEquals("BEFORE", alert.getText());
|
||||
alert.set(true);
|
||||
assertTrue(isAlertActive("BEFORE", Level.LOW));
|
||||
alert.set(false);
|
||||
assertFalse(isAlertActive("BEFORE", Level.LOW));
|
||||
alert.setText("AFTER");
|
||||
assertEquals("AFTER", alert.getText());
|
||||
alert.set(true);
|
||||
assertFalse(isAlertActive("BEFORE", Level.LOW));
|
||||
assertTrue(isAlertActive("AFTER", Level.LOW));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void setTextWhileSet() {
|
||||
try (var alert = makeAlert("BEFORE", Level.LOW)) {
|
||||
assertEquals("BEFORE", alert.getText());
|
||||
alert.set(true);
|
||||
assertTrue(isAlertActive("BEFORE", Level.LOW));
|
||||
alert.setText("AFTER");
|
||||
assertEquals("AFTER", alert.getText());
|
||||
assertFalse(isAlertActive("BEFORE", Level.LOW));
|
||||
assertTrue(isAlertActive("AFTER", Level.LOW));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,243 +0,0 @@
|
||||
// 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.util;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.TestInfo;
|
||||
import org.junit.jupiter.api.parallel.ResourceLock;
|
||||
import org.wpilib.networktables.NetworkTableInstance;
|
||||
import org.wpilib.networktables.StringArraySubscriber;
|
||||
import org.wpilib.simulation.SimHooks;
|
||||
import org.wpilib.smartdashboard.SmartDashboard;
|
||||
import org.wpilib.util.Alert.AlertType;
|
||||
|
||||
class AlertTest {
|
||||
private NetworkTableInstance m_inst;
|
||||
private String m_groupName;
|
||||
|
||||
@BeforeEach
|
||||
void setup(TestInfo info) {
|
||||
m_groupName = "AlertTest_" + info.getDisplayName();
|
||||
|
||||
m_inst = NetworkTableInstance.create();
|
||||
SmartDashboard.setNetworkTableInstance(m_inst);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void checkClean() {
|
||||
update();
|
||||
assertEquals(0, getActiveAlerts(AlertType.kError).length);
|
||||
assertEquals(0, getActiveAlerts(AlertType.kWarning).length);
|
||||
assertEquals(0, getActiveAlerts(AlertType.kInfo).length);
|
||||
|
||||
m_inst.close();
|
||||
SmartDashboard.setNetworkTableInstance(NetworkTableInstance.getDefault());
|
||||
}
|
||||
|
||||
private String getSubtableName(Alert.AlertType type) {
|
||||
switch (type) {
|
||||
case kError:
|
||||
return "errors";
|
||||
case kWarning:
|
||||
return "warnings";
|
||||
case kInfo:
|
||||
return "infos";
|
||||
default:
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
private StringArraySubscriber getSubscriberForType(Alert.AlertType type) {
|
||||
return m_inst
|
||||
.getStringArrayTopic("/SmartDashboard/" + m_groupName + "/" + getSubtableName(type))
|
||||
.subscribe(new String[] {});
|
||||
}
|
||||
|
||||
private String[] getActiveAlerts(AlertType type) {
|
||||
update();
|
||||
try (var sub = getSubscriberForType(type)) {
|
||||
return sub.get();
|
||||
}
|
||||
}
|
||||
|
||||
private void update() {
|
||||
SmartDashboard.updateValues();
|
||||
}
|
||||
|
||||
private boolean isAlertActive(String text, Alert.AlertType type) {
|
||||
return Arrays.asList(getActiveAlerts(type)).contains(text);
|
||||
}
|
||||
|
||||
private void assertState(Alert.AlertType type, List<String> state) {
|
||||
assertEquals(state, Arrays.asList(getActiveAlerts(type)));
|
||||
}
|
||||
|
||||
private Alert makeAlert(String text, Alert.AlertType type) {
|
||||
return new Alert(m_groupName, text, type);
|
||||
}
|
||||
|
||||
@Test
|
||||
void setUnsetSingle() {
|
||||
try (var one = makeAlert("one", AlertType.kInfo)) {
|
||||
assertFalse(isAlertActive("one", AlertType.kInfo));
|
||||
one.set(true);
|
||||
assertTrue(isAlertActive("one", AlertType.kInfo));
|
||||
one.set(false);
|
||||
assertFalse(isAlertActive("one", AlertType.kInfo));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void setUnsetMultiple() {
|
||||
try (var one = makeAlert("one", AlertType.kError);
|
||||
var two = makeAlert("two", AlertType.kInfo)) {
|
||||
assertFalse(isAlertActive("one", AlertType.kError));
|
||||
assertFalse(isAlertActive("two", AlertType.kInfo));
|
||||
one.set(true);
|
||||
assertTrue(isAlertActive("one", AlertType.kError));
|
||||
assertFalse(isAlertActive("two", AlertType.kInfo));
|
||||
one.set(true);
|
||||
two.set(true);
|
||||
assertTrue(isAlertActive("one", AlertType.kError));
|
||||
assertTrue(isAlertActive("two", AlertType.kInfo));
|
||||
one.set(false);
|
||||
assertFalse(isAlertActive("one", AlertType.kError));
|
||||
assertTrue(isAlertActive("two", AlertType.kInfo));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void setIsIdempotent() {
|
||||
try (var a = makeAlert("A", AlertType.kInfo);
|
||||
var b = makeAlert("B", AlertType.kInfo);
|
||||
var c = makeAlert("C", AlertType.kInfo)) {
|
||||
a.set(true);
|
||||
b.set(true);
|
||||
c.set(true);
|
||||
|
||||
var startState = List.of(getActiveAlerts(AlertType.kInfo));
|
||||
|
||||
b.set(true);
|
||||
assertState(AlertType.kInfo, startState);
|
||||
|
||||
a.set(true);
|
||||
assertState(AlertType.kInfo, startState);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void closeUnsetsAlert() {
|
||||
try (var alert = makeAlert("alert", AlertType.kWarning)) {
|
||||
alert.set(true);
|
||||
assertTrue(isAlertActive("alert", AlertType.kWarning));
|
||||
}
|
||||
assertFalse(isAlertActive("alert", AlertType.kWarning));
|
||||
}
|
||||
|
||||
@Test
|
||||
void setTextWhileUnset() {
|
||||
try (var alert = makeAlert("BEFORE", AlertType.kInfo)) {
|
||||
assertEquals("BEFORE", alert.getText());
|
||||
alert.set(true);
|
||||
assertTrue(isAlertActive("BEFORE", AlertType.kInfo));
|
||||
alert.set(false);
|
||||
assertFalse(isAlertActive("BEFORE", AlertType.kInfo));
|
||||
alert.setText("AFTER");
|
||||
assertEquals("AFTER", alert.getText());
|
||||
alert.set(true);
|
||||
assertFalse(isAlertActive("BEFORE", AlertType.kInfo));
|
||||
assertTrue(isAlertActive("AFTER", AlertType.kInfo));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void setTextWhileSet() {
|
||||
try (var alert = makeAlert("BEFORE", AlertType.kInfo)) {
|
||||
assertEquals("BEFORE", alert.getText());
|
||||
alert.set(true);
|
||||
assertTrue(isAlertActive("BEFORE", AlertType.kInfo));
|
||||
alert.setText("AFTER");
|
||||
assertEquals("AFTER", alert.getText());
|
||||
assertFalse(isAlertActive("BEFORE", AlertType.kInfo));
|
||||
assertTrue(isAlertActive("AFTER", AlertType.kInfo));
|
||||
}
|
||||
}
|
||||
|
||||
@ResourceLock("timing")
|
||||
@Test
|
||||
void setTextDoesNotAffectFirstOrderSort() {
|
||||
SimHooks.pauseTiming();
|
||||
try (var a = makeAlert("A", AlertType.kInfo);
|
||||
var b = makeAlert("B", AlertType.kInfo);
|
||||
var c = makeAlert("C", AlertType.kInfo)) {
|
||||
a.set(true);
|
||||
SimHooks.stepTiming(1);
|
||||
b.set(true);
|
||||
SimHooks.stepTiming(1);
|
||||
c.set(true);
|
||||
|
||||
var expectedEndState = new ArrayList<>(List.of(getActiveAlerts(AlertType.kInfo)));
|
||||
expectedEndState.replaceAll(s -> "B".equals(s) ? "AFTER" : s);
|
||||
b.setText("AFTER");
|
||||
|
||||
assertState(AlertType.kInfo, expectedEndState);
|
||||
} finally {
|
||||
SimHooks.resumeTiming();
|
||||
}
|
||||
}
|
||||
|
||||
@ResourceLock("timing")
|
||||
@Test
|
||||
void sortOrder() {
|
||||
SimHooks.pauseTiming();
|
||||
try (var a = makeAlert("A", AlertType.kInfo);
|
||||
var b = makeAlert("B", AlertType.kInfo);
|
||||
var c = makeAlert("C", AlertType.kInfo)) {
|
||||
a.set(true);
|
||||
assertState(AlertType.kInfo, List.of("A"));
|
||||
SimHooks.stepTiming(1);
|
||||
b.set(true);
|
||||
assertState(AlertType.kInfo, List.of("B", "A"));
|
||||
SimHooks.stepTiming(1);
|
||||
c.set(true);
|
||||
assertState(AlertType.kInfo, List.of("C", "B", "A"));
|
||||
|
||||
SimHooks.stepTiming(1);
|
||||
c.set(false);
|
||||
assertState(AlertType.kInfo, List.of("B", "A"));
|
||||
|
||||
SimHooks.stepTiming(1);
|
||||
c.set(true);
|
||||
assertState(AlertType.kInfo, List.of("C", "B", "A"));
|
||||
|
||||
SimHooks.stepTiming(1);
|
||||
a.set(false);
|
||||
assertState(AlertType.kInfo, List.of("C", "B"));
|
||||
|
||||
SimHooks.stepTiming(1);
|
||||
b.set(false);
|
||||
assertState(AlertType.kInfo, List.of("C"));
|
||||
|
||||
SimHooks.stepTiming(1);
|
||||
b.set(true);
|
||||
assertState(AlertType.kInfo, List.of("B", "C"));
|
||||
|
||||
SimHooks.stepTiming(1);
|
||||
a.set(true);
|
||||
assertState(AlertType.kInfo, List.of("A", "B", "C"));
|
||||
} finally {
|
||||
SimHooks.resumeTiming();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user