diff --git a/photon-core/src/main/java/org/photonvision/common/logging/Logger.java b/photon-core/src/main/java/org/photonvision/common/logging/Logger.java index c60911a727..012ddf35ce 100644 --- a/photon-core/src/main/java/org/photonvision/common/logging/Logger.java +++ b/photon-core/src/main/java/org/photonvision/common/logging/Logger.java @@ -17,6 +17,9 @@ package org.photonvision.common.logging; +import edu.wpi.first.cscore.CameraServerJNI; +import edu.wpi.first.networktables.PubSubOption; +import edu.wpi.first.networktables.StringPublisher; import java.io.*; import java.nio.file.Path; import java.text.ParseException; @@ -28,6 +31,7 @@ import org.photonvision.common.configuration.PathManager; import org.photonvision.common.dataflow.DataChangeService; import org.photonvision.common.dataflow.events.OutgoingUIEvent; +import org.photonvision.common.dataflow.networktables.NetworkTablesManager; import org.photonvision.common.util.TimedTaskManager; @SuppressWarnings("unused") @@ -123,6 +127,11 @@ public static void addFileAppender(Path logFilePath) { currentAppenders.add(new FileLogAppender(logFilePath)); } + /** Publish log stringst to NT. This must be done AFTER loading ntcore. */ + public static void addNtAppender() { + currentAppenders.add(new NTLogAppender()); + } + public static void cleanLogs(Path folderToClean) { File[] logs = folderToClean.toFile().listFiles(); if (logs == null) return; @@ -305,6 +314,20 @@ public void log(String message, LogLevel level) { } } + private static class NTLogAppender implements LogAppender { + private StringPublisher pub = + NetworkTablesManager.getInstance() + .kRootTable + .getSubTable("log_outputs") + .getStringTopic(CameraServerJNI.getHostname()) + .publish(PubSubOption.sendAll(true)); + + @Override + public void log(String message, LogLevel level) { + pub.accept(message); + } + } + private static class FileLogAppender implements LogAppender { private OutputStream out; private boolean wantsFlush; diff --git a/photon-lib/src/main/java/org/photonvision/PhotonCamera.java b/photon-lib/src/main/java/org/photonvision/PhotonCamera.java index a67438949a..5b1b6b2428 100644 --- a/photon-lib/src/main/java/org/photonvision/PhotonCamera.java +++ b/photon-lib/src/main/java/org/photonvision/PhotonCamera.java @@ -45,8 +45,9 @@ import edu.wpi.first.networktables.StringSubscriber; import edu.wpi.first.wpilibj.DriverStation; import edu.wpi.first.wpilibj.Timer; +import java.util.List; import java.util.Optional; -import java.util.Set; +import java.util.stream.Collectors; import org.photonvision.common.hardware.VisionLEDMode; import org.photonvision.common.networktables.PacketSubscriber; import org.photonvision.targeting.PhotonPipelineResult; @@ -74,6 +75,8 @@ public class PhotonCamera implements AutoCloseable { IntegerSubscriber heartbeatEntry; DoubleArraySubscriber cameraIntrinsicsSubscriber; DoubleArraySubscriber cameraDistortionSubscriber; + MultiSubscriber m_topicNameSubscriber; + NetworkTable rootPhotonTable; @Override public void close() { @@ -97,6 +100,7 @@ public void close() { pipelineIndexRequest.close(); cameraIntrinsicsSubscriber.close(); cameraDistortionSubscriber.close(); + m_topicNameSubscriber.close(); } private final String path; @@ -124,8 +128,8 @@ public static void setVersionCheckEnabled(boolean enabled) { */ public PhotonCamera(NetworkTableInstance instance, String cameraName) { name = cameraName; - var photonvision_root_table = instance.getTable(kTableName); - this.cameraTable = photonvision_root_table.getSubTable(cameraName); + rootPhotonTable = instance.getTable(kTableName); + this.cameraTable = rootPhotonTable.getSubTable(cameraName); path = cameraTable.getPath(); var rawBytesEntry = cameraTable @@ -147,14 +151,17 @@ public PhotonCamera(NetworkTableInstance instance, String cameraName) { cameraDistortionSubscriber = cameraTable.getDoubleArrayTopic("cameraDistortion").subscribe(null); - ledModeRequest = photonvision_root_table.getIntegerTopic("ledModeRequest").publish(); - ledModeState = photonvision_root_table.getIntegerTopic("ledModeState").subscribe(-1); - versionEntry = photonvision_root_table.getStringTopic("version").subscribe(""); + ledModeRequest = rootPhotonTable.getIntegerTopic("ledModeRequest").publish(); + ledModeState = rootPhotonTable.getIntegerTopic("ledModeState").subscribe(-1); + versionEntry = rootPhotonTable.getStringTopic("version").subscribe(""); // Existing is enough to make this multisubscriber do its thing - MultiSubscriber m_topicNameSubscriber = + m_topicNameSubscriber = new MultiSubscriber( - instance, new String[] {"/photonvision/"}, PubSubOption.topicsOnly(true)); + instance, + new String[] {"/photonvision/"}, + PubSubOption.topicsOnly(true), + PubSubOption.disableLocal(true)); HAL.report(tResourceType.kResourceType_PhotonCamera, InstanceCount); InstanceCount++; @@ -346,10 +353,10 @@ private void verifyVersion() { // Heartbeat entry is assumed to always be present. If it's not present, we // assume that a camera with that name was never connected in the first place. if (!heartbeatEntry.exists()) { - Set cameraNames = cameraTable.getInstance().getTable(kTableName).getSubTables(); + var cameraNames = getTablesThatLookLikePhotonCameras(); if (cameraNames.isEmpty()) { DriverStation.reportError( - "Could not find any PhotonVision coprocessors on NetworkTables. Double check that PhotonVision is running, and that your camera is connected!", + "Could not find **any** PhotonVision coprocessors on NetworkTables. Double check that PhotonVision is running, and that your camera is connected!", false); } else { DriverStation.reportError( @@ -357,9 +364,17 @@ private void verifyVersion() { + path + " not found on NetworkTables. Double check that your camera names match!", true); + + var cameraNameStr = new StringBuilder(); + for (var c : cameraNames) { + cameraNameStr.append(" ==> "); + cameraNameStr.append(c); + cameraNameStr.append("\n"); + } + DriverStation.reportError( - "Found the following PhotonVision cameras on NetworkTables:\n" - + String.join("\n", cameraNames), + "Found the following PhotonVision cameras active on NetworkTables:\n" + + String.join("\n", cameraNameStr), false); } } @@ -404,4 +419,13 @@ else if (!isConnected()) { throw new UnsupportedOperationException(versionMismatchMessage); } } + + private List getTablesThatLookLikePhotonCameras() { + return rootPhotonTable.getSubTables().stream() + .filter( + it -> { + return rootPhotonTable.getSubTable(it).getEntry("rawBytes").exists(); + }) + .collect(Collectors.toList()); + } } diff --git a/photon-server/src/main/java/org/photonvision/Main.java b/photon-server/src/main/java/org/photonvision/Main.java index 52a3225bed..87f9dfbe89 100644 --- a/photon-server/src/main/java/org/photonvision/Main.java +++ b/photon-server/src/main/java/org/photonvision/Main.java @@ -358,6 +358,9 @@ public static void main(String[] args) { logger.error("Failed to load native libraries!", e); System.exit(1); } + + Logger.addNtAppender(); + logger.info("Native libraries loaded."); try {