diff --git a/photon-core/src/main/java/org/photonvision/common/dataflow/networktables/NetworkTablesManager.java b/photon-core/src/main/java/org/photonvision/common/dataflow/networktables/NetworkTablesManager.java
index e713edcbb2..bd04dce0d8 100644
--- a/photon-core/src/main/java/org/photonvision/common/dataflow/networktables/NetworkTablesManager.java
+++ b/photon-core/src/main/java/org/photonvision/common/dataflow/networktables/NetworkTablesManager.java
@@ -41,6 +41,7 @@
import org.photonvision.common.dataflow.events.OutgoingUIEvent;
import org.photonvision.common.dataflow.websocket.UIPhotonConfiguration;
import org.photonvision.common.hardware.HardwareManager;
+import org.photonvision.common.hardware.alerts.AlertGroups;
import org.photonvision.common.logging.LogGroup;
import org.photonvision.common.logging.LogLevel;
import org.photonvision.common.logging.Logger;
@@ -66,9 +67,9 @@ public class NetworkTablesManager {
new MultiSubscriber(ntInstance, new String[] {kRootTableName + "/" + kCoprocTableName + "/"});
// Creating the alert up here since it should be persistent
- private final Alert conflictAlert = new Alert("PhotonAlerts", "", AlertType.kWarning);
+ private final Alert conflictAlert = new Alert(AlertGroups.PHOTON_ALERTS, "", AlertType.kWarning);
- private final Alert mismatchAlert = new Alert("PhotonAlerts", "", AlertType.kWarning);
+ private final Alert mismatchAlert = new Alert(AlertGroups.PHOTON_ALERTS, "", AlertType.kWarning);
public boolean conflictingHostname = false;
public String conflictingCameras = "";
diff --git a/photon-core/src/main/java/org/photonvision/common/hardware/alerts/AlertGroups.java b/photon-core/src/main/java/org/photonvision/common/hardware/alerts/AlertGroups.java
new file mode 100644
index 0000000000..ffed8ce437
--- /dev/null
+++ b/photon-core/src/main/java/org/photonvision/common/hardware/alerts/AlertGroups.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) Photon Vision.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package org.photonvision.common.hardware.alerts;
+
+/** Shared alert-group names for WPILib Alert instances. */
+public final class AlertGroups {
+ public static final String PHOTON_ALERTS = "PhotonAlerts";
+
+ private AlertGroups() {}
+}
diff --git a/photon-core/src/main/java/org/photonvision/common/hardware/metrics/SystemMonitor.java b/photon-core/src/main/java/org/photonvision/common/hardware/metrics/SystemMonitor.java
index a9401649be..9beeea85d5 100644
--- a/photon-core/src/main/java/org/photonvision/common/hardware/metrics/SystemMonitor.java
+++ b/photon-core/src/main/java/org/photonvision/common/hardware/metrics/SystemMonitor.java
@@ -20,6 +20,9 @@
import edu.wpi.first.cscore.CameraServerJNI;
import edu.wpi.first.networktables.NetworkTable;
import edu.wpi.first.networktables.ProtobufPublisher;
+import edu.wpi.first.wpilibj.Alert;
+import edu.wpi.first.wpilibj.Alert.AlertType;
+import edu.wpi.first.wpilibj.smartdashboard.SmartDashboard;
import java.io.IOException;
import java.nio.file.FileStore;
import java.nio.file.Files;
@@ -32,6 +35,7 @@
import org.photonvision.common.dataflow.events.OutgoingUIEvent;
import org.photonvision.common.dataflow.networktables.NetworkTablesManager;
import org.photonvision.common.hardware.Platform;
+import org.photonvision.common.hardware.alerts.AlertGroups;
import org.photonvision.common.logging.LogGroup;
import org.photonvision.common.logging.Logger;
import org.photonvision.common.networking.NetworkUtils;
@@ -62,6 +66,9 @@ private record NetworkTraffic(double sentBitRate, double recvBitRate) {}
.getProtobufTopic(CameraServerJNI.getHostname(), DeviceMetrics.proto)
.publish();
+ private final Alert thermalThrottlingAlert =
+ new Alert(AlertGroups.PHOTON_ALERTS, "Thermal throttling detected!", AlertType.kWarning);
+
private SystemInfo si;
private CentralProcessor cpu;
private OperatingSystem os;
@@ -140,11 +147,11 @@ protected SystemMonitor() {
}
/**
- * Returns a comma-separated list of addtional thermal zone types that should be checked to get
+ * Returns a comma-separated list of additional thermal zone types that should be checked to get
* the CPU temperature on Unix systems. The temperature will be reported for the first temperature
- * zone with a type that mateches an item of this list. If the CPU temperature isn't being
- * reported correctly for a coprocessor, override this method to return a string with type
- * associated with the thermal zone for that comprocessor.
+ * zone with a type that matches an item of this list. If the CPU temperature isn't being reported
+ * correctly for a coprocessor, override this method to return a string with type associated with
+ * the thermal zone for that coprocessor.
*
* @return String containing a comma-separated list of thermal zone types for reading CPU
* temperature.
@@ -159,7 +166,7 @@ protected String getThermalZoneTypes() {
/**
* Starts the periodic system monitor that publishes performance metrics. The metrics are
- * published every millisUpdateInerval seconds after a millisStartDelay startup delay. Calling
+ * published every millisUpdateInterval seconds after a millisStartDelay startup delay. Calling
* this method when the monitor is running will stop it and restart it with the new delay and
* update interval.
*
@@ -210,6 +217,9 @@ private void publishMetrics() {
metricPublisher.set(metrics);
+ thermalThrottlingAlert.set(this.isThermallyThrottling());
+ SmartDashboard.updateValues();
+
if (writeMetricsToLog) {
logMetrics(metrics);
}
@@ -233,7 +243,7 @@ private void logMetrics(DeviceMetrics metrics) {
String.format("CPU Throttle: %s, ", metrics.cpuThr().isBlank() ? "N/A" : metrics.cpuThr()));
sb.append(
String.format(
- "Data sent: %.0f Kbps, Data recieved: %.0f Kbps",
+ "Data sent: %.0f Kbps, Data received: %.0f Kbps",
metrics.sentBitRate() / 1000, metrics.recvBitRate() / 1000));
logger.debug(sb.toString());
}
@@ -446,6 +456,16 @@ public String getCpuThrottleReason() {
return "";
}
+ /**
+ * Returns true if the device is currently experiencing thermal throttling. Platforms that support
+ * thermal throttling detection will override this method.
+ *
+ * @return true if thermally throttling, false otherwise.
+ */
+ public boolean isThermallyThrottling() {
+ return false;
+ }
+
/**
* Returns the total GPU memory in MiB.
*
@@ -475,7 +495,7 @@ public String getIpAddress() {
}
/**
- * Returns a NetworkTraffic instance containing the average sent and recieved network traffic
+ * Returns a NetworkTraffic instance containing the average sent and received network traffic
* since the last time this was called.
*
* @return NetworkTraffic instance with data in bits/second. The traffic values will be -1 if the
@@ -537,7 +557,7 @@ private void testSM() {
() -> {
var nt = getNetworkTraffic();
return String.format(
- "Data sent: %.0f Kbps, Data recieved: %.0f Kbps",
+ "Data sent: %.0f Kbps, Data received: %.0f Kbps",
nt.sentBitRate() / 1000, nt.recvBitRate() / 1000);
});
diff --git a/photon-core/src/main/java/org/photonvision/common/hardware/metrics/SystemMonitorRaspberryPi.java b/photon-core/src/main/java/org/photonvision/common/hardware/metrics/SystemMonitorRaspberryPi.java
index cd241447aa..4ce4e91fd1 100644
--- a/photon-core/src/main/java/org/photonvision/common/hardware/metrics/SystemMonitorRaspberryPi.java
+++ b/photon-core/src/main/java/org/photonvision/common/hardware/metrics/SystemMonitorRaspberryPi.java
@@ -25,13 +25,7 @@ public class SystemMonitorRaspberryPi extends SystemMonitor {
@Override
public String getCpuThrottleReason() {
- int state = 0;
- String output = vcgencmd("get_throttled");
- try {
- state = Integer.decode(output);
- } catch (NumberFormatException e) {
- logger.warn("Could not parse return value: " + output);
- }
+ int state = getThrottleState();
if ((state & 0x01) != 0) {
return "LOW VOLTAGE";
} else if ((state & 0x08) != 0) {
@@ -44,6 +38,22 @@ public String getCpuThrottleReason() {
return "None";
}
+ @Override
+ public boolean isThermallyThrottling() {
+ return (getThrottleState() & 0x08) != 0;
+ }
+
+ private int getThrottleState() {
+ int state = 0;
+ String output = vcgencmd("get_throttled");
+ try {
+ state = Integer.decode(output);
+ } catch (NumberFormatException e) {
+ logger.warn("Could not parse return value: " + output);
+ }
+ return state;
+ }
+
@Override
public double getGpuMem() {
String output = vcgencmd("get_mem gpu");
diff --git a/photon-lib/src/main/java/org/photonvision/AlertGroups.java b/photon-lib/src/main/java/org/photonvision/AlertGroups.java
new file mode 100644
index 0000000000..fbbb1f2b5e
--- /dev/null
+++ b/photon-lib/src/main/java/org/photonvision/AlertGroups.java
@@ -0,0 +1,32 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) PhotonVision
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package org.photonvision;
+
+/** Shared alert-group names for WPILib Alert instances. */
+public final class AlertGroups {
+ public static final String PHOTON_ALERTS = "PhotonAlerts";
+
+ private AlertGroups() {}
+}
diff --git a/photon-lib/src/main/java/org/photonvision/PhotonCamera.java b/photon-lib/src/main/java/org/photonvision/PhotonCamera.java
index d548e17f09..c0b2f6efaf 100644
--- a/photon-lib/src/main/java/org/photonvision/PhotonCamera.java
+++ b/photon-lib/src/main/java/org/photonvision/PhotonCamera.java
@@ -59,7 +59,6 @@
public class PhotonCamera implements AutoCloseable {
private static int InstanceCount = 1;
public static final String kTableName = "photonvision";
- private static final String PHOTON_ALERT_GROUP = "PhotonAlerts";
private final NetworkTable cameraTable;
PacketSubscriber resultSubscriber;
@@ -138,8 +137,10 @@ public PhotonCamera(NetworkTableInstance instance, String cameraName) {
name = cameraName;
disconnectAlert =
new Alert(
- PHOTON_ALERT_GROUP, "PhotonCamera '" + name + "' is disconnected.", AlertType.kWarning);
- timesyncAlert = new Alert(PHOTON_ALERT_GROUP, "", AlertType.kWarning);
+ AlertGroups.PHOTON_ALERTS,
+ "PhotonCamera '" + name + "' is disconnected.",
+ AlertType.kWarning);
+ timesyncAlert = new Alert(AlertGroups.PHOTON_ALERTS, "", AlertType.kWarning);
rootPhotonTable = instance.getTable(kTableName);
this.cameraTable = rootPhotonTable.getSubTable(cameraName);
path = cameraTable.getPath();
diff --git a/photon-lib/src/main/native/cpp/photon/PhotonCamera.cpp b/photon-lib/src/main/native/cpp/photon/PhotonCamera.cpp
index 1c4833c4bf..9451cd70d1 100644
--- a/photon-lib/src/main/native/cpp/photon/PhotonCamera.cpp
+++ b/photon-lib/src/main/native/cpp/photon/PhotonCamera.cpp
@@ -39,6 +39,7 @@
#include
#include "PhotonVersion.h"
+#include "photon/AlertGroups.h"
#include "photon/dataflow/structures/Packet.h"
static constexpr units::second_t WARN_DEBOUNCE_SEC = 5_s;
@@ -109,7 +110,6 @@ namespace photon {
constexpr const units::second_t VERSION_CHECK_INTERVAL = 5_s;
static const std::vector PHOTON_PREFIX = {"/photonvision/"};
-static const std::string PHOTON_ALERT_GROUP{"PhotonAlerts"};
bool PhotonCamera::VERSION_CHECK_ENABLED = true;
void PhotonCamera::SetVersionCheckEnabled(bool enabled) {
@@ -160,11 +160,12 @@ PhotonCamera::PhotonCamera(nt::NetworkTableInstance instance,
topicNameSubscriber(instance, PHOTON_PREFIX, {.topicsOnly = true}),
path(rootTable->GetPath()),
cameraName(cameraName),
- disconnectAlert(PHOTON_ALERT_GROUP,
+ disconnectAlert(AlertGroups::kPhotonAlerts,
std::string{"PhotonCamera '"} + std::string{cameraName} +
"' is disconnected.",
frc::Alert::AlertType::kWarning),
- timesyncAlert(PHOTON_ALERT_GROUP, "", frc::Alert::AlertType::kWarning) {
+ timesyncAlert(AlertGroups::kPhotonAlerts, "",
+ frc::Alert::AlertType::kWarning) {
verifyDependencies();
HAL_Report(HALUsageReporting::kResourceType_PhotonCamera, InstanceCount);
InstanceCount++;
diff --git a/photon-lib/src/main/native/include/photon/AlertGroups.h b/photon-lib/src/main/native/include/photon/AlertGroups.h
new file mode 100644
index 0000000000..a57ec95b3e
--- /dev/null
+++ b/photon-lib/src/main/native/include/photon/AlertGroups.h
@@ -0,0 +1,33 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) PhotonVision
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#pragma once
+
+#include
+
+namespace photon::AlertGroups {
+
+inline constexpr std::string_view kPhotonAlerts = "PhotonAlerts";
+
+} // namespace photon::AlertGroups
diff --git a/photon-lib/src/test/java/org/photonvision/PhotonCameraTest.java b/photon-lib/src/test/java/org/photonvision/PhotonCameraTest.java
index 9a2f69dd42..25e41db73a 100644
--- a/photon-lib/src/test/java/org/photonvision/PhotonCameraTest.java
+++ b/photon-lib/src/test/java/org/photonvision/PhotonCameraTest.java
@@ -310,6 +310,7 @@ public void testAlerts() throws InterruptedException {
// GIVEN a fresh NT instance
var cameraName = "foobar";
+ var warningAlertKey = AlertGroups.PHOTON_ALERTS + "/warnings";
// AND a photoncamera that is disconnected
var camera = new PhotonCamera(inst, cameraName);
@@ -327,7 +328,7 @@ public void testAlerts() throws InterruptedException {
// The alert state will be set (hard-coded here)
assertTrue(
- Arrays.stream(SmartDashboard.getStringArray("PhotonAlerts/warnings", new String[0]))
+ Arrays.stream(SmartDashboard.getStringArray(warningAlertKey, new String[0]))
.anyMatch(it -> it.equals(disconnectedCameraString)));
Thread.sleep(20);
@@ -356,11 +357,11 @@ public void testAlerts() throws InterruptedException {
// THEN the camera isn't disconnected
assertTrue(
- Arrays.stream(SmartDashboard.getStringArray("PhotonAlerts/warnings", new String[0]))
+ Arrays.stream(SmartDashboard.getStringArray(warningAlertKey, new String[0]))
.noneMatch(it -> it.equals(disconnectedCameraString)));
// AND the alert string looks like a timesync warning
assertTrue(
- Arrays.stream(SmartDashboard.getStringArray("PhotonAlerts/warnings", new String[0]))
+ Arrays.stream(SmartDashboard.getStringArray(warningAlertKey, new String[0]))
.filter(it -> it.contains("is not connected to the TimeSyncServer"))
.count()
== 1);
diff --git a/photon-lib/src/test/native/cpp/PhotonCameraTest.cpp b/photon-lib/src/test/native/cpp/PhotonCameraTest.cpp
index e021299206..fb417cdb6c 100644
--- a/photon-lib/src/test/native/cpp/PhotonCameraTest.cpp
+++ b/photon-lib/src/test/native/cpp/PhotonCameraTest.cpp
@@ -32,6 +32,7 @@
#include
#include
#include
+#include
#include
#include
@@ -61,6 +62,8 @@ TEST(TimeSyncProtocolTest, Smoketest) {
TEST(PhotonCameraTest, Alerts) {
using frc::SmartDashboard;
+ const std::string warningsAlertKey =
+ std::string{photon::AlertGroups::kPhotonAlerts} + "/warnings";
// GIVEN a local-only NT instance
auto inst = nt::NetworkTableInstance::GetDefault();
@@ -86,7 +89,7 @@ TEST(PhotonCameraTest, Alerts) {
SmartDashboard::UpdateValues();
// The alert state will be set (hard-coded here)
- auto alerts = SmartDashboard::GetStringArray("PhotonAlerts/warnings", {});
+ auto alerts = SmartDashboard::GetStringArray(warningsAlertKey, {});
EXPECT_TRUE(
std::any_of(alerts.begin(), alerts.end(),
[&disconnectedCameraString](const std::string& alert) {
@@ -113,7 +116,7 @@ TEST(PhotonCameraTest, Alerts) {
SmartDashboard::UpdateValues();
// THEN the camera isn't disconnected
- auto alerts = SmartDashboard::GetStringArray("PhotonAlerts/warnings", {});
+ auto alerts = SmartDashboard::GetStringArray(warningsAlertKey, {});
fmt::println("{}:{}: saw alerts: {}", __FILE__, __LINE__, alerts);
EXPECT_TRUE(
std::none_of(alerts.begin(), alerts.end(),