From 0ac46f4b6c070c4a9c4eb78a2c5071f93883685f Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 16 Mar 2026 22:00:10 +0000 Subject: [PATCH] macOS support + NT results-only publishing - Mark macOS as a supported platform in Platform.java - Remove the platform support check in Main.java that exited on macOS - Remove all camera settings from NT publishing (pipeline index, driver mode, fps limit, camera intrinsics/distortion) - Remove the apriltag field layout subscriber from NT - NTDataPublisher now only publishes vision results to NT - PhotonCameraSim publishes intrinsics/distortion directly instead of through NTTopicSet https://claude.ai/code/session_01EfivnKEs8qbAeEBHL6BoNn --- .../networktables/NTDataPublisher.java | 117 +----------------- .../networktables/NetworkTablesManager.java | 30 ----- .../vision/processes/VisionModule.java | 8 +- .../simulation/PhotonCameraSim.java | 17 ++- .../src/main/java/org/photonvision/Main.java | 7 -- .../common/hardware/Platform.java | 2 +- .../common/networktables/NTTopicSet.java | 46 ------- 7 files changed, 19 insertions(+), 208 deletions(-) diff --git a/photon-core/src/main/java/org/photonvision/common/dataflow/networktables/NTDataPublisher.java b/photon-core/src/main/java/org/photonvision/common/dataflow/networktables/NTDataPublisher.java index 7eff9733..53344b43 100644 --- a/photon-core/src/main/java/org/photonvision/common/dataflow/networktables/NTDataPublisher.java +++ b/photon-core/src/main/java/org/photonvision/common/dataflow/networktables/NTDataPublisher.java @@ -19,12 +19,8 @@ import edu.wpi.first.math.geometry.Transform3d; import edu.wpi.first.networktables.NetworkTable; -import edu.wpi.first.networktables.NetworkTableEvent; import edu.wpi.first.networktables.NetworkTablesJNI; import java.util.List; -import java.util.function.BooleanSupplier; -import java.util.function.Consumer; -import java.util.function.Supplier; import org.photonvision.common.configuration.ConfigManager; import org.photonvision.common.dataflow.CVPipelineResultConsumer; import org.photonvision.common.logging.LogGroup; @@ -43,111 +39,17 @@ public class NTDataPublisher implements CVPipelineResultConsumer { private final NTTopicSet ts = new NTTopicSet(); - NTDataChangeListener pipelineIndexListener; - private final Supplier pipelineIndexSupplier; - private final Consumer pipelineIndexConsumer; - - NTDataChangeListener driverModeListener; - private final BooleanSupplier driverModeSupplier; - private final Consumer driverModeConsumer; - - NTDataChangeListener fpsLimitListener; - private final Consumer fpsLimitConsumer; - private final Supplier fpsLimitSupplier; - - public NTDataPublisher( - String cameraNickname, - Supplier pipelineIndexSupplier, - Consumer pipelineIndexConsumer, - BooleanSupplier driverModeSupplier, - Consumer driverModeConsumer, - Supplier fpsLimitSupplier, - Consumer fpsLimitConsumer) { - this.pipelineIndexSupplier = pipelineIndexSupplier; - this.pipelineIndexConsumer = pipelineIndexConsumer; - this.driverModeSupplier = driverModeSupplier; - this.driverModeConsumer = driverModeConsumer; - this.fpsLimitSupplier = fpsLimitSupplier; - this.fpsLimitConsumer = fpsLimitConsumer; - + public NTDataPublisher(String cameraNickname) { updateCameraNickname(cameraNickname); - updateEntries(); - } - - private void onPipelineIndexChange(NetworkTableEvent entryNotification) { - var newIndex = (int) entryNotification.valueData.value.getInteger(); - var originalIndex = pipelineIndexSupplier.get(); - - // ignore indexes below 0 - if (newIndex < 0) { - ts.pipelineIndexPublisher.set(originalIndex); - return; - } - - if (newIndex == originalIndex) { - logger.debug("Pipeline index is already " + newIndex); - return; - } - - pipelineIndexConsumer.accept(newIndex); - var setIndex = pipelineIndexSupplier.get(); - if (newIndex != setIndex) { // set failed - ts.pipelineIndexPublisher.set(setIndex); - // TODO: Log - } - logger.debug("Set pipeline index to " + newIndex); - } - - private void onDriverModeChange(NetworkTableEvent entryNotification) { - var newDriverMode = entryNotification.valueData.value.getBoolean(); - var originalDriverMode = driverModeSupplier.getAsBoolean(); - - if (newDriverMode == originalDriverMode) { - logger.debug("Driver mode is already " + newDriverMode); - return; - } - - driverModeConsumer.accept(newDriverMode); - logger.debug("Set driver mode to " + newDriverMode); - } - - private void onFPSLimitChange(NetworkTableEvent entryNotification) { - var newFPSLimit = (int) entryNotification.valueData.value.getInteger(); - var originalFPSLimit = fpsLimitSupplier.get(); - - if (newFPSLimit == originalFPSLimit) { - logger.debug("FPS limit is already " + newFPSLimit); - return; - } - - fpsLimitConsumer.accept(newFPSLimit); - logger.debug("Set FPS limit to " + newFPSLimit); + ts.updateEntries(); } private void removeEntries() { - if (pipelineIndexListener != null) pipelineIndexListener.remove(); - if (driverModeListener != null) driverModeListener.remove(); ts.removeEntries(); } private void updateEntries() { - if (pipelineIndexListener != null) pipelineIndexListener.remove(); - if (driverModeListener != null) driverModeListener.remove(); - if (fpsLimitListener != null) fpsLimitListener.remove(); - ts.updateEntries(); - - pipelineIndexListener = - new NTDataChangeListener( - ts.subTable.getInstance(), ts.pipelineIndexRequestSub, this::onPipelineIndexChange); - - driverModeListener = - new NTDataChangeListener( - ts.subTable.getInstance(), ts.driverModeSubscriber, this::onDriverModeChange); - - fpsLimitListener = - new NTDataChangeListener( - ts.subTable.getInstance(), ts.fpsLimitSubscriber, this::onFPSLimitChange); } public void updateCameraNickname(String newCameraNickname) { @@ -194,9 +96,6 @@ public void accept(CVPipelineResult result) { ts.protoResultPublisher.set(simplified); } - ts.pipelineIndexPublisher.set(pipelineIndexSupplier.get()); - ts.driverModePublisher.set(driverModeSupplier.getAsBoolean()); - ts.fpsLimitPublisher.set(fpsLimitSupplier.get()); ts.latencyMillisEntry.set(acceptedResult.getLatencyMillis()); ts.fpsEntry.set(acceptedResult.fps); ts.hasTargetEntry.set(acceptedResult.hasTargets()); @@ -225,18 +124,6 @@ public void accept(CVPipelineResult result) { ts.bestTargetPosY.set(0); } - // Something in the result can sometimes be null -- so check probably too many things - if (acceptedResult.inputAndOutputFrame != null - && acceptedResult.inputAndOutputFrame.frameStaticProperties != null - && acceptedResult.inputAndOutputFrame.frameStaticProperties.cameraCalibration != null) { - var fsp = acceptedResult.inputAndOutputFrame.frameStaticProperties; - ts.cameraIntrinsicsPublisher.accept(fsp.cameraCalibration.getIntrinsicsArr()); - ts.cameraDistortionPublisher.accept(fsp.cameraCalibration.getDistCoeffsArr()); - } else { - ts.cameraIntrinsicsPublisher.accept(new double[0]); - ts.cameraDistortionPublisher.accept(new double[0]); - } - ts.heartbeatPublisher.set(acceptedResult.sequenceID); // TODO...nt4... is this needed? 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 e713edcb..39a9a2ac 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 @@ -17,7 +17,6 @@ package org.photonvision.common.dataflow.networktables; -import edu.wpi.first.apriltag.AprilTagFieldLayout; import edu.wpi.first.cscore.CameraServerJNI; import edu.wpi.first.networktables.LogMessage; import edu.wpi.first.networktables.MultiSubscriber; @@ -25,13 +24,10 @@ import edu.wpi.first.networktables.NetworkTableEvent; import edu.wpi.first.networktables.NetworkTableEvent.Kind; import edu.wpi.first.networktables.NetworkTableInstance; -import edu.wpi.first.networktables.StringSubscriber; 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.util.Arrays; -import java.util.EnumSet; import java.util.HashMap; import org.photonvision.PhotonVersion; import org.photonvision.common.configuration.CameraConfiguration; @@ -46,7 +42,6 @@ import org.photonvision.common.logging.Logger; import org.photonvision.common.networking.NetworkUtils; import org.photonvision.common.util.TimedTaskManager; -import org.photonvision.common.util.file.JacksonUtils; public class NetworkTablesManager { private static final Logger logger = @@ -56,7 +51,6 @@ public class NetworkTablesManager { private final String kRootTableName = "/photonvision"; // The coprocessors table should only be used for operations/data related to MAC address public final String kCoprocTableName = "coprocessors"; - private final String kFieldLayoutName = "apriltag_field_layout"; public final NetworkTable kRootTable = ntInstance.getTable(kRootTableName); public final NetworkTable kCoprocTable = kRootTable.getSubTable(kCoprocTableName); @@ -76,9 +70,6 @@ public class NetworkTablesManager { private boolean m_isRetryingConnection = false; - private StringSubscriber m_fieldLayoutSubscriber = - kRootTable.getStringTopic(kFieldLayoutName).subscribe(""); - private final TimeSyncManager m_timeSync = new TimeSyncManager(kRootTable); NTDriverStation ntDriverStation; @@ -88,9 +79,6 @@ private NetworkTablesManager() { LogMessage.kInfo, LogMessage.kCritical, this::logNtMessage); // to hide error messages ntInstance.addConnectionListener(true, this::checkNtConnectState); // to hide error messages - ntInstance.addListener( - m_fieldLayoutSubscriber, EnumSet.of(Kind.kValueAll), this::onFieldLayoutChanged); - ntDriverStation = new NTDriverStation(this.getNTInst()); // This should start as false, since we don't know if there's a conflict yet @@ -195,24 +183,6 @@ public NetworkTableInstance getNTInst() { return ntInstance; } - private void onFieldLayoutChanged(NetworkTableEvent event) { - var atfl_json = event.valueData.value.getString(); - try { - System.out.println("Got new field layout!"); - var atfl = JacksonUtils.deserialize(atfl_json, AprilTagFieldLayout.class); - ConfigManager.getInstance().getConfig().setApriltagFieldLayout(atfl); - ConfigManager.getInstance().requestSave(); - DataChangeService.getInstance() - .publishEvent( - new OutgoingUIEvent<>( - "fullsettings", - UIPhotonConfiguration.programStateToUi(ConfigManager.getInstance().getConfig()))); - } catch (IOException e) { - logger.error("Error deserializing atfl!"); - logger.error(atfl_json); - } - } - public void broadcastConnectedStatus() { TimedTaskManager.getInstance().addOneShotTask(this::broadcastConnectedStatusImpl, 1000L); } diff --git a/photon-core/src/main/java/org/photonvision/vision/processes/VisionModule.java b/photon-core/src/main/java/org/photonvision/vision/processes/VisionModule.java index b57b01d9..6f72f7d6 100644 --- a/photon-core/src/main/java/org/photonvision/vision/processes/VisionModule.java +++ b/photon-core/src/main/java/org/photonvision/vision/processes/VisionModule.java @@ -147,13 +147,7 @@ public VisionModule(PipelineManager pipelineManager, VisionSource visionSource) ntConsumer = new NTDataPublisher( - visionSource.getSettables().getConfiguration().nickname, - pipelineManager::getRequestedIndex, - this::setPipeline, - pipelineManager::getDriverMode, - this::setDriverMode, - this::getFPSLimit, - this::setFPSLimit); + visionSource.getSettables().getConfiguration().nickname); uiDataConsumer = new UIDataPublisher(visionSource.getSettables().getConfiguration().uniqueName); statusLEDsConsumer = new StatusLEDConsumer(visionSource.getSettables().getConfiguration().uniqueName); diff --git a/photon-lib/src/main/java/org/photonvision/simulation/PhotonCameraSim.java b/photon-lib/src/main/java/org/photonvision/simulation/PhotonCameraSim.java index c4331ba8..0de33c22 100644 --- a/photon-lib/src/main/java/org/photonvision/simulation/PhotonCameraSim.java +++ b/photon-lib/src/main/java/org/photonvision/simulation/PhotonCameraSim.java @@ -34,6 +34,7 @@ import edu.wpi.first.math.Pair; import edu.wpi.first.math.geometry.Pose3d; import edu.wpi.first.math.geometry.Transform3d; +import edu.wpi.first.networktables.DoubleArrayPublisher; import edu.wpi.first.util.PixelFormat; import edu.wpi.first.util.WPIUtilJNI; import edu.wpi.first.wpilibj.RobotController; @@ -72,6 +73,9 @@ public class PhotonCameraSim implements AutoCloseable { @SuppressWarnings("doclint") protected NTTopicSet ts = new NTTopicSet(); + private DoubleArrayPublisher cameraIntrinsicsPublisher; + private DoubleArrayPublisher cameraDistortionPublisher; + private long heartbeatCounter = 1; /** This simulated camera's {@link SimCameraProperties} */ @@ -106,6 +110,8 @@ public void close() { videoSimFrameRaw.release(); videoSimProcessed.close(); videoSimFrameProcessed.release(); + if (cameraIntrinsicsPublisher != null) cameraIntrinsicsPublisher.close(); + if (cameraDistortionPublisher != null) cameraDistortionPublisher.close(); } /** @@ -162,6 +168,13 @@ public PhotonCameraSim( ts.removeEntries(); ts.subTable = camera.getCameraTable(); ts.updateEntries(); + + if (cameraIntrinsicsPublisher != null) cameraIntrinsicsPublisher.close(); + if (cameraDistortionPublisher != null) cameraDistortionPublisher.close(); + cameraIntrinsicsPublisher = + ts.subTable.getDoubleArrayTopic("cameraIntrinsics").publish(); + cameraDistortionPublisher = + ts.subTable.getDoubleArrayTopic("cameraDistortion").publish(); } /** @@ -697,8 +710,8 @@ public void submitProcessedFrame(PhotonPipelineResult result, long receiveTimest ts.targetPoseEntry.set(transform, receiveTimestamp); } - ts.cameraIntrinsicsPublisher.set(prop.getIntrinsics().getData(), receiveTimestamp); - ts.cameraDistortionPublisher.set(prop.getDistCoeffs().getData(), receiveTimestamp); + cameraIntrinsicsPublisher.set(prop.getIntrinsics().getData(), receiveTimestamp); + cameraDistortionPublisher.set(prop.getDistCoeffs().getData(), receiveTimestamp); ts.heartbeatPublisher.set(heartbeatCounter, receiveTimestamp); heartbeatCounter += 1; diff --git a/photon-server/src/main/java/org/photonvision/Main.java b/photon-server/src/main/java/org/photonvision/Main.java index b07538a5..4b322c8b 100644 --- a/photon-server/src/main/java/org/photonvision/Main.java +++ b/photon-server/src/main/java/org/photonvision/Main.java @@ -257,13 +257,6 @@ public static void main(String[] args) { logger.error("Failed to parse command-line options!", e); } - // We don't want to trigger an exit in test mode or smoke test. This is - // specifically for MacOS. - if (!(Platform.isSupported() || isSmoketest || isTestMode)) { - logger.error("This platform is unsupported!"); - System.exit(1); - } - try { boolean success = LoadJNI.loadLibraries(); diff --git a/photon-targeting/src/main/java/org/photonvision/common/hardware/Platform.java b/photon-targeting/src/main/java/org/photonvision/common/hardware/Platform.java index 340eed92..ee700dc8 100644 --- a/photon-targeting/src/main/java/org/photonvision/common/hardware/Platform.java +++ b/photon-targeting/src/main/java/org/photonvision/common/hardware/Platform.java @@ -68,7 +68,7 @@ public enum Platform { // Completely unsupported WINDOWS_32("Windows x86", Platform::getUnknownModel, false, OSType.WINDOWS, false), - MACOS("Mac OS", Platform::getUnknownModel, false, OSType.MACOS, false), + MACOS("Mac OS", Platform::getUnknownModel, false, OSType.MACOS, true), LINUX_ARM32( "Linux ARM32", Platform::getUnknownModel, false, OSType.LINUX, false), // ODROID XU4, C1+ UNKNOWN("Unsupported Platform", Platform::getUnknownModel, false, OSType.UNKNOWN, false); diff --git a/photon-targeting/src/main/java/org/photonvision/common/networktables/NTTopicSet.java b/photon-targeting/src/main/java/org/photonvision/common/networktables/NTTopicSet.java index 5b3d50d0..1d43b55e 100644 --- a/photon-targeting/src/main/java/org/photonvision/common/networktables/NTTopicSet.java +++ b/photon-targeting/src/main/java/org/photonvision/common/networktables/NTTopicSet.java @@ -19,12 +19,8 @@ import edu.wpi.first.math.geometry.Transform3d; import edu.wpi.first.networktables.BooleanPublisher; -import edu.wpi.first.networktables.BooleanSubscriber; -import edu.wpi.first.networktables.BooleanTopic; -import edu.wpi.first.networktables.DoubleArrayPublisher; import edu.wpi.first.networktables.DoublePublisher; import edu.wpi.first.networktables.IntegerPublisher; -import edu.wpi.first.networktables.IntegerSubscriber; import edu.wpi.first.networktables.IntegerTopic; import edu.wpi.first.networktables.NetworkTable; import edu.wpi.first.networktables.ProtobufPublisher; @@ -47,16 +43,6 @@ public class NTTopicSet { public PacketPublisher resultPublisher; public ProtobufPublisher protoResultPublisher; - public IntegerPublisher pipelineIndexPublisher; - public IntegerSubscriber pipelineIndexRequestSub; - - public BooleanTopic driverModeEntry; - public BooleanPublisher driverModePublisher; - public BooleanSubscriber driverModeSubscriber; - - public IntegerPublisher fpsLimitPublisher; - public IntegerSubscriber fpsLimitSubscriber; - public DoublePublisher latencyMillisEntry; public DoublePublisher fpsEntry; public BooleanPublisher hasTargetEntry; @@ -74,10 +60,6 @@ public class NTTopicSet { public IntegerTopic heartbeatTopic; public IntegerPublisher heartbeatPublisher; - // Camera Calibration - public DoubleArrayPublisher cameraIntrinsicsPublisher; - public DoubleArrayPublisher cameraDistortionPublisher; - public void updateEntries() { var rawBytesEntry = subTable @@ -95,20 +77,6 @@ public void updateEntries() { .getProtobufTopic("result_proto", PhotonPipelineResult.proto) .publish(PubSubOption.periodic(0.01), PubSubOption.sendAll(true)); - pipelineIndexPublisher = subTable.getIntegerTopic("pipelineIndexState").publish(); - pipelineIndexRequestSub = subTable.getIntegerTopic("pipelineIndexRequest").subscribe(0); - - driverModePublisher = subTable.getBooleanTopic("driverMode").publish(); - driverModeSubscriber = subTable.getBooleanTopic("driverModeRequest").subscribe(false); - - // Fun little hack to make the request show up - driverModeSubscriber.getTopic().publish().setDefault(false); - - fpsLimitPublisher = subTable.getIntegerTopic("fpsLimit").publish(); - fpsLimitSubscriber = subTable.getIntegerTopic("fpsLimitRequest").subscribe(-1); - - fpsLimitSubscriber.getTopic().publish().setDefault(-1); - latencyMillisEntry = subTable.getDoubleTopic("latencyMillis").publish(); fpsEntry = subTable.getDoubleTopic("fps").publish(); hasTargetEntry = subTable.getBooleanTopic("hasTarget").publish(); @@ -124,22 +92,11 @@ public void updateEntries() { heartbeatTopic = subTable.getIntegerTopic("heartbeat"); heartbeatPublisher = heartbeatTopic.publish(); - - cameraIntrinsicsPublisher = subTable.getDoubleArrayTopic("cameraIntrinsics").publish(); - cameraDistortionPublisher = subTable.getDoubleArrayTopic("cameraDistortion").publish(); } @SuppressWarnings("DuplicatedCode") public void removeEntries() { if (resultPublisher != null) resultPublisher.close(); - if (pipelineIndexPublisher != null) pipelineIndexPublisher.close(); - if (pipelineIndexRequestSub != null) pipelineIndexRequestSub.close(); - - if (driverModePublisher != null) driverModePublisher.close(); - if (driverModeSubscriber != null) driverModeSubscriber.close(); - - if (fpsLimitPublisher != null) fpsLimitPublisher.close(); - if (fpsLimitSubscriber != null) fpsLimitSubscriber.close(); if (latencyMillisEntry != null) latencyMillisEntry.close(); if (fpsEntry != null) fpsEntry.close(); @@ -153,8 +110,5 @@ public void removeEntries() { if (bestTargetPosY != null) bestTargetPosY.close(); if (heartbeatPublisher != null) heartbeatPublisher.close(); - - if (cameraIntrinsicsPublisher != null) cameraIntrinsicsPublisher.close(); - if (cameraDistortionPublisher != null) cameraDistortionPublisher.close(); } }