001/* 002 * Copyright (C) Photon Vision. 003 * 004 * This program is free software: you can redistribute it and/or modify 005 * it under the terms of the GNU General Public License as published by 006 * the Free Software Foundation, either version 3 of the License, or 007 * (at your option) any later version. 008 * 009 * This program is distributed in the hope that it will be useful, 010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 012 * GNU General Public License for more details. 013 * 014 * You should have received a copy of the GNU General Public License 015 * along with this program. If not, see <https://www.gnu.org/licenses/>. 016 */ 017 018package org.photonvision; 019 020import edu.wpi.first.hal.HAL; 021import java.io.IOException; 022import java.nio.file.Path; 023import java.util.ArrayList; 024import java.util.List; 025import org.apache.commons.cli.*; 026import org.photonvision.common.configuration.CameraConfiguration; 027import org.photonvision.common.configuration.ConfigManager; 028import org.photonvision.common.configuration.NeuralNetworkModelManager; 029import org.photonvision.common.dataflow.networktables.NetworkTablesManager; 030import org.photonvision.common.hardware.HardwareManager; 031import org.photonvision.common.hardware.OsImageVersion; 032import org.photonvision.common.hardware.PiVersion; 033import org.photonvision.common.hardware.Platform; 034import org.photonvision.common.logging.KernelLogLogger; 035import org.photonvision.common.logging.LogGroup; 036import org.photonvision.common.logging.LogLevel; 037import org.photonvision.common.logging.Logger; 038import org.photonvision.common.logging.PvCSCoreLogger; 039import org.photonvision.common.networking.NetworkManager; 040import org.photonvision.common.util.TestUtils; 041import org.photonvision.jni.PhotonTargetingJniLoader; 042import org.photonvision.jni.RknnDetectorJNI; 043import org.photonvision.mrcal.MrCalJNILoader; 044import org.photonvision.raspi.LibCameraJNILoader; 045import org.photonvision.server.Server; 046import org.photonvision.vision.apriltag.AprilTagFamily; 047import org.photonvision.vision.camera.PVCameraInfo; 048import org.photonvision.vision.opencv.CVMat; 049import org.photonvision.vision.pipeline.AprilTagPipelineSettings; 050import org.photonvision.vision.pipeline.CVPipelineSettings; 051import org.photonvision.vision.pipeline.PipelineProfiler; 052import org.photonvision.vision.processes.VisionSourceManager; 053import org.photonvision.vision.target.TargetModel; 054 055public class Main { 056 public static final int DEFAULT_WEBPORT = 5800; 057 058 private static final Logger logger = new Logger(Main.class, LogGroup.General); 059 private static final boolean isRelease = PhotonVersion.isRelease; 060 061 private static boolean isTestMode = false; 062 private static boolean isSmoketest = false; 063 private static Path testModeFolder = null; 064 private static boolean printDebugLogs; 065 066 private static boolean handleArgs(String[] args) throws ParseException { 067 final var options = new Options(); 068 options.addOption("d", "debug", false, "Enable debug logging prints"); 069 options.addOption("h", "help", false, "Show this help text and exit"); 070 options.addOption( 071 "t", 072 "test-mode", 073 false, 074 "Run in test mode with 2019 and 2020 WPI field images in place of cameras"); 075 076 options.addOption("p", "path", true, "Point test mode to a specific folder"); 077 options.addOption("n", "disable-networking", false, "Disables control device network settings"); 078 options.addOption( 079 "c", 080 "clear-config", 081 false, 082 "Clears PhotonVision pipeline and networking settings. Preserves log files"); 083 options.addOption( 084 "s", 085 "smoketest", 086 false, 087 "Exit Photon after loading native libraries and camera configs, but before starting up camera runners"); 088 089 CommandLineParser parser = new DefaultParser(); 090 CommandLine cmd = parser.parse(options, args); 091 092 if (cmd.hasOption("help")) { 093 HelpFormatter formatter = new HelpFormatter(); 094 formatter.printHelp("java -jar photonvision.jar [options]", options); 095 return false; // exit program 096 } else { 097 if (cmd.hasOption("debug")) { 098 printDebugLogs = true; 099 logger.info("Enabled debug logging"); 100 } 101 102 if (cmd.hasOption("test-mode")) { 103 isTestMode = true; 104 logger.info("Running in test mode - Cameras will not be used"); 105 106 if (cmd.hasOption("path")) { 107 Path p = Path.of(System.getProperty("PATH_PREFIX", "") + cmd.getOptionValue("path")); 108 logger.info("Loading from Path " + p.toAbsolutePath().toString()); 109 testModeFolder = p; 110 } 111 } 112 113 if (cmd.hasOption("disable-networking")) { 114 NetworkManager.getInstance().networkingIsDisabled = true; 115 } 116 117 if (cmd.hasOption("clear-config")) { 118 ConfigManager.getInstance().clearConfig(); 119 } 120 121 if (cmd.hasOption("smoketest")) { 122 isSmoketest = true; 123 } 124 } 125 return true; 126 } 127 128 private static void addTestModeSources() { 129 ConfigManager.getInstance().load(); 130 131 CameraConfiguration camConf2024 = 132 ConfigManager.getInstance().getConfig().getCameraConfigurations().get("WPI2024"); 133 if (camConf2024 == null || true) { 134 camConf2024 = 135 new CameraConfiguration( 136 PVCameraInfo.fromFileInfo( 137 TestUtils.getResourcesFolderPath(true) 138 .resolve("testimages") 139 .resolve(TestUtils.WPI2024Images.kSpeakerCenter_143in.path) 140 .toString(), 141 "WPI2024")); 142 143 camConf2024.FOV = TestUtils.WPI2024Images.FOV; 144 // same camera as 2023 145 camConf2024.calibrations.add(TestUtils.get2023LifeCamCoeffs(true)); 146 147 var pipeline2024 = new AprilTagPipelineSettings(); 148 var path_split = Path.of(camConf2024.matchedCameraInfo.path()).getFileName().toString(); 149 pipeline2024.pipelineNickname = path_split.replace(".jpg", ""); 150 pipeline2024.targetModel = TargetModel.kAprilTag6p5in_36h11; 151 pipeline2024.tagFamily = AprilTagFamily.kTag36h11; 152 pipeline2024.inputShouldShow = true; 153 pipeline2024.solvePNPEnabled = true; 154 155 var psList2024 = new ArrayList<CVPipelineSettings>(); 156 psList2024.add(pipeline2024); 157 camConf2024.pipelineSettings = psList2024; 158 } 159 160 var cameraConfigs = List.of(camConf2024); 161 162 ConfigManager.getInstance().unloadCameraConfigs(); 163 cameraConfigs.stream().forEach(ConfigManager.getInstance()::addCameraConfiguration); 164 VisionSourceManager.getInstance().registerLoadedConfigs(cameraConfigs); 165 } 166 167 public static void main(String[] args) { 168 logger.info( 169 "Starting PhotonVision version " 170 + PhotonVersion.versionString 171 + " on platform " 172 + Platform.getPlatformName() 173 + (Platform.isRaspberryPi() ? (" (Pi " + PiVersion.getPiVersion() + ")") : "")); 174 175 if (OsImageVersion.IMAGE_VERSION.isPresent()) { 176 logger.info("PhotonVision image version: " + OsImageVersion.IMAGE_VERSION.get()); 177 } 178 179 try { 180 if (!handleArgs(args)) { 181 System.exit(1); 182 } 183 } catch (ParseException e) { 184 logger.error("Failed to parse command-line options!", e); 185 } 186 187 // We don't want to trigger an exit in test mode or smoke test. This is specifically for MacOS. 188 if (!(Platform.isSupported() || isSmoketest || isTestMode)) { 189 logger.error("This platform is unsupported!"); 190 System.exit(1); 191 } 192 193 try { 194 boolean success = TestUtils.loadLibraries(); 195 196 if (!success) { 197 logger.error("Failed to load native libraries! Giving up :("); 198 System.exit(1); 199 } 200 } catch (Exception e) { 201 logger.error("Failed to load native libraries!", e); 202 System.exit(1); 203 } 204 logger.info("WPI JNI libraries loaded."); 205 206 try { 207 boolean success = PhotonTargetingJniLoader.load(); 208 209 if (!success) { 210 logger.error("Failed to load native libraries! Giving up :("); 211 System.exit(1); 212 } 213 } catch (Exception e) { 214 logger.error("Failed to load photon-targeting JNI!", e); 215 System.exit(1); 216 } 217 logger.info("photon-targeting JNI libraries loaded."); 218 219 if (!HAL.initialize(500, 0)) { 220 logger.error("Failed to initialize the HAL! Giving up :("); 221 System.exit(1); 222 } 223 224 try { 225 if (Platform.isRaspberryPi()) { 226 LibCameraJNILoader.forceLoad(); 227 } 228 } catch (IOException e) { 229 logger.error("Failed to load libcamera-JNI!", e); 230 } 231 try { 232 if (Platform.isRK3588()) { 233 RknnDetectorJNI.forceLoad(); 234 } else { 235 logger.error("Platform does not support RKNN based machine learning!"); 236 } 237 } catch (IOException e) { 238 logger.error("Failed to load rknn-JNI!", e); 239 } 240 try { 241 MrCalJNILoader.forceLoad(); 242 } catch (IOException e) { 243 logger.warn( 244 "Failed to load mrcal-JNI! Camera calibration will fall back to opencv\n" 245 + e.getMessage()); 246 } 247 248 CVMat.enablePrint(false); 249 PipelineProfiler.enablePrint(false); 250 251 var logLevel = printDebugLogs ? LogLevel.TRACE : LogLevel.DEBUG; 252 Logger.setLevel(LogGroup.Camera, logLevel); 253 Logger.setLevel(LogGroup.WebServer, logLevel); 254 Logger.setLevel(LogGroup.VisionModule, logLevel); 255 Logger.setLevel(LogGroup.Data, logLevel); 256 Logger.setLevel(LogGroup.Config, logLevel); 257 Logger.setLevel(LogGroup.General, logLevel); 258 logger.info("Logging initialized in debug mode."); 259 260 // Add Linux kernel log->Photon logger 261 KernelLogLogger.getInstance(); 262 263 // Add CSCore->Photon logger 264 PvCSCoreLogger.getInstance(); 265 266 logger.debug("Loading ConfigManager..."); 267 ConfigManager.getInstance().load(); // init config manager 268 ConfigManager.getInstance().requestSave(); 269 270 logger.info("Loading ML models..."); 271 var modelManager = NeuralNetworkModelManager.getInstance(); 272 modelManager.extractModels(ConfigManager.getInstance().getModelsDirectory()); 273 modelManager.discoverModels(ConfigManager.getInstance().getModelsDirectory()); 274 275 logger.debug("Loading HardwareManager..."); 276 // Force load the hardware manager 277 HardwareManager.getInstance(); 278 279 logger.debug("Loading NetworkManager..."); 280 NetworkManager.getInstance().reinitialize(); 281 282 logger.debug("Loading NetworkTablesManager..."); 283 NetworkTablesManager.getInstance() 284 .setConfig(ConfigManager.getInstance().getConfig().getNetworkConfig()); 285 NetworkTablesManager.getInstance().registerTimedTasks(); 286 287 if (isSmoketest) { 288 logger.info("PhotonVision base functionality loaded -- smoketest complete"); 289 System.exit(0); 290 } 291 292 // todo - should test mode just add test mode sources, but still allow local usb cameras to be 293 // added? 294 if (!isTestMode) { 295 logger.debug("Loading VisionSourceManager..."); 296 VisionSourceManager.getInstance() 297 .registerLoadedConfigs( 298 ConfigManager.getInstance().getConfig().getCameraConfigurations().values()); 299 } else { 300 if (testModeFolder == null) { 301 addTestModeSources(); 302 } 303 } 304 305 VisionSourceManager.getInstance().registerTimedTasks(); 306 307 logger.info("Starting server..."); 308 HardwareManager.getInstance().setRunning(true); 309 Server.initialize(DEFAULT_WEBPORT); 310 } 311}