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.vision.camera.USBCameras; 019 020import edu.wpi.first.cameraserver.CameraServer; 021import edu.wpi.first.cscore.UsbCamera; 022import edu.wpi.first.cscore.VideoException; 023import edu.wpi.first.cscore.VideoProperty; 024import java.util.*; 025import org.photonvision.common.configuration.CameraConfiguration; 026import org.photonvision.common.hardware.Platform; 027import org.photonvision.common.logging.LogGroup; 028import org.photonvision.common.logging.Logger; 029import org.photonvision.vision.camera.CameraQuirk; 030import org.photonvision.vision.camera.PVCameraInfo.PVUsbCameraInfo; 031import org.photonvision.vision.camera.QuirkyCamera; 032import org.photonvision.vision.frame.FrameProvider; 033import org.photonvision.vision.frame.provider.USBFrameProvider; 034import org.photonvision.vision.processes.VisionSource; 035import org.photonvision.vision.processes.VisionSourceSettables; 036 037public class USBCameraSource extends VisionSource { 038 private final Logger logger; 039 private final UsbCamera camera; 040 protected GenericUSBCameraSettables settables; 041 protected FrameProvider usbFrameProvider; 042 043 private void onCameraConnected() { 044 // Aid to the development team - record the properties available for whatever the user plugged 045 // in 046 printCameraProperaties(); 047 048 settables.onCameraConnected(); 049 } 050 051 public USBCameraSource(CameraConfiguration config) { 052 super(config); 053 054 logger = new Logger(USBCameraSource.class, config.nickname, LogGroup.Camera); 055 056 if (!(config.matchedCameraInfo instanceof PVUsbCameraInfo)) { 057 logger.error( 058 "USBCameraSource matched to a non-USB camera info?? " 059 + config.matchedCameraInfo.toString()); 060 } 061 062 camera = new UsbCamera(config.nickname, config.getDevicePath()); 063 064 // TODO - I don't need this, do I? 065 // // set vid/pid if not done already for future matching 066 // if (config.usbVID <= 0) config.usbVID = this.camera.getInfo().vendorId; 067 // if (config.usbPID <= 0) config.usbPID = this.camera.getInfo().productId; 068 069 // TODO - why do we delegate this to USBCameraSource? Quirks are part of the CameraConfig?? 070 // also TODO - is the config's saved usb info a reasonable guess for quirk detection? seems like 071 // yes to me... 072 if (getCameraConfiguration().cameraQuirks == null) { 073 int vid = 074 (config.matchedCameraInfo instanceof PVUsbCameraInfo) 075 ? ((PVUsbCameraInfo) config.matchedCameraInfo).vendorId 076 : -1; 077 int pid = 078 (config.matchedCameraInfo instanceof PVUsbCameraInfo) 079 ? ((PVUsbCameraInfo) config.matchedCameraInfo).productId 080 : -1; 081 082 getCameraConfiguration().cameraQuirks = 083 QuirkyCamera.getQuirkyCamera(vid, pid, config.matchedCameraInfo.name()); 084 } 085 086 if (getCameraConfiguration().cameraQuirks.hasQuirks()) { 087 logger.info("Quirky camera detected: " + getCameraConfiguration().cameraQuirks); 088 } 089 090 var cameraBroken = getCameraConfiguration().cameraQuirks.hasQuirk(CameraQuirk.CompletelyBroken); 091 092 if (cameraBroken) { 093 // Known issues - Disable this camera 094 logger.info( 095 "Camera " 096 + getCameraConfiguration().cameraQuirks.baseName 097 + " is not supported for PhotonVision"); 098 // set some defaults, as these should never be used. 099 settables = null; 100 usbFrameProvider = null; 101 102 } else { 103 // Camera is likely to work, set up the Settables 104 settables = createSettables(config, camera); 105 logger.info("Created settables " + settables); 106 107 usbFrameProvider = new USBFrameProvider(camera, settables, this::onCameraConnected); 108 } 109 } 110 111 /** 112 * Factory for making appropriate settables 113 * 114 * @param config 115 * @param camera 116 * @return 117 */ 118 protected GenericUSBCameraSettables createSettables( 119 CameraConfiguration config, UsbCamera camera) { 120 var quirks = getCameraConfiguration().cameraQuirks; 121 122 GenericUSBCameraSettables settables; 123 124 if (quirks.hasQuirk(CameraQuirk.LifeCamControls)) { 125 if (Platform.isWindows()) { 126 logger.debug("Using Microsoft Lifecam 3000 Windows-Specific Settables"); 127 settables = new LifeCam3kWindowsCameraSettables(config, camera); 128 } else { 129 logger.debug("Using Microsoft Lifecam 3000 Settables"); 130 settables = new LifeCam3kCameraSettables(config, camera); 131 } 132 } else if (quirks.hasQuirk(CameraQuirk.PsEyeControls)) { 133 logger.debug("Using PlayStation Eye Camera Settables"); 134 settables = new PsEyeCameraSettables(config, camera); 135 } else if (quirks.hasQuirk(CameraQuirk.ArduOV2311Controls)) { 136 if (Platform.isWindows()) { 137 logger.debug("Using Arducam OV2311 Windows-Specific Settables"); 138 settables = new ArduOV2311WindowsCameraSettables(config, camera); 139 } else { 140 logger.debug("Using Arducam OV2311 Settables"); 141 settables = new ArduOV2311CameraSettables(config, camera); 142 } 143 } else if (quirks.hasQuirk(CameraQuirk.ArduOV9281Controls)) { 144 logger.debug("Using Arducam OV9281 Settables"); 145 settables = new InnoOV9281CameraSettables(config, camera); 146 } else if (quirks.hasQuirk(CameraQuirk.ArduOV9782Controls)) { 147 logger.debug("Using Arducam OV9782 Settables"); 148 settables = new ArduOV9782CameraSettables(config, camera); 149 } else if (quirks.hasQuirk(CameraQuirk.InnoOV9281Controls)) { 150 settables = new InnoOV9281CameraSettables(config, camera); 151 } else if (quirks.hasQuirk(CameraQuirk.See3Cam_24CUG)) { 152 settables = new See3Cam24CUGSettables(config, camera); 153 } else { 154 logger.debug("Using Generic USB Cam Settables"); 155 settables = new GenericUSBCameraSettables(config, camera); 156 } 157 158 return settables; 159 } 160 161 /** 162 * Must be called after createSettables Using the current config/camera and modified quirks, make 163 * a new settables 164 */ 165 public void remakeSettables() { 166 var oldConfig = this.cameraConfiguration; 167 var oldCamera = this.camera; 168 169 // Re-create settables 170 var oldVideoMode = this.settables.getCurrentVideoMode(); 171 this.settables = createSettables(oldConfig, oldCamera); 172 173 // Settables only cache videomodes on connect - force this to happen next tick 174 if (settables.camera.isConnected()) { 175 this.settables.onCameraConnected(); 176 } else { 177 this.usbFrameProvider.cameraPropertiesCached = false; 178 } 179 180 // And update the settables' FrameStaticProps 181 settables.setVideoMode(oldVideoMode); 182 183 // Propagate our updated settables over to the frame provider 184 ((USBFrameProvider) this.usbFrameProvider).updateSettables(this.settables); 185 } 186 187 private void printCameraProperaties() { 188 VideoProperty[] cameraProperties = null; 189 try { 190 cameraProperties = camera.enumerateProperties(); 191 } catch (VideoException e) { 192 logger.error("Failed to list camera properties!", e); 193 } 194 195 if (cameraProperties != null) { 196 String cameraPropertiesStr = "Cam Properties Dump:\n"; 197 for (int i = 0; i < cameraProperties.length; i++) { 198 cameraPropertiesStr += 199 "Name: " 200 + cameraProperties[i].getName() 201 + ", Kind: " 202 + cameraProperties[i].getKind() 203 + ", Value: " 204 + cameraProperties[i].getKind().getValue() 205 + ", Min: " 206 + cameraProperties[i].getMin() 207 + ", Max: " 208 + cameraProperties[i].getMax() 209 + ", Dflt: " 210 + cameraProperties[i].getDefault() 211 + ", Step: " 212 + cameraProperties[i].getStep() 213 + "\n"; 214 } 215 logger.debug(cameraPropertiesStr); 216 } 217 } 218 219 public QuirkyCamera getCameraQuirks() { 220 return getCameraConfiguration().cameraQuirks; 221 } 222 223 @Override 224 public FrameProvider getFrameProvider() { 225 return usbFrameProvider; 226 } 227 228 @Override 229 public VisionSourceSettables getSettables() { 230 return this.settables; 231 } 232 233 @Override 234 public boolean isVendorCamera() { 235 return false; // Vendors do not supply USB Cameras 236 } 237 238 @Override 239 public boolean hasLEDs() { 240 return false; // Assume USB cameras do not have photonvision-controlled LEDs 241 } 242 243 @Override 244 public void release() { 245 CameraServer.removeCamera(camera.getName()); 246 camera.close(); 247 usbFrameProvider.release(); 248 usbFrameProvider = null; 249 } 250 251 @Override 252 public boolean equals(Object obj) { 253 if (this == obj) return true; 254 if (obj == null) return false; 255 if (getClass() != obj.getClass()) return false; 256 USBCameraSource other = (USBCameraSource) obj; 257 if (camera == null) { 258 if (other.camera != null) return false; 259 } else if (!camera.equals(other.camera)) return false; 260 if (settables == null) { 261 if (other.settables != null) return false; 262 } else if (!settables.equals(other.settables)) return false; 263 if (usbFrameProvider == null) { 264 if (other.usbFrameProvider != null) return false; 265 } else if (!usbFrameProvider.equals(other.usbFrameProvider)) return false; 266 if (getCameraConfiguration().cameraQuirks == null) { 267 if (other.getCameraConfiguration().cameraQuirks != null) return false; 268 } else if (!getCameraConfiguration() 269 .cameraQuirks 270 .equals(other.getCameraConfiguration().cameraQuirks)) return false; 271 return true; 272 } 273 274 @Override 275 public int hashCode() { 276 return Objects.hash( 277 camera, 278 settables, 279 usbFrameProvider, 280 cameraConfiguration, 281 getCameraConfiguration().cameraQuirks); 282 } 283}