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.common.hardware; 019 020import java.io.BufferedReader; 021import java.io.File; 022import java.io.IOException; 023import java.nio.file.Files; 024import java.nio.file.Paths; 025import java.util.function.Supplier; 026import org.photonvision.jni.CombinedRuntimeLoader; 027 028@SuppressWarnings({"unused", "doclint"}) 029public enum Platform { 030 // WPILib Supported (JNI) 031 WINDOWS_64("Windows x64", Platform::getUnknownModel, false, OSType.WINDOWS, true), 032 LINUX_32("Linux x86", Platform::getUnknownModel, false, OSType.LINUX, true), 033 LINUX_64("Linux x64", Platform::getUnknownModel, false, OSType.LINUX, true), 034 LINUX_RASPBIAN32( 035 "Linux Raspbian 32-bit", 036 Platform::getLinuxDeviceTreeModel, 037 true, 038 OSType.LINUX, 039 true), // Raspberry Pi 3/4 with a 32-bit image 040 LINUX_RASPBIAN64( 041 "Linux Raspbian 64-bit", 042 Platform::getLinuxDeviceTreeModel, 043 true, 044 OSType.LINUX, 045 true), // Raspberry Pi 3/4 with a 64-bit image 046 LINUX_RK3588_64( 047 "Linux AARCH 64-bit with RK3588", 048 Platform::getLinuxDeviceTreeModel, 049 false, 050 OSType.LINUX, 051 true), 052 LINUX_QCS6490( 053 "Linux AARCH 64-bit with QCS6490", 054 Platform::getLinuxDeviceTreeModel, 055 false, 056 OSType.LINUX, 057 true), // QCS6490 SBCs 058 LINUX_AARCH64( 059 "Linux AARCH64", 060 Platform::getLinuxDeviceTreeModel, 061 false, 062 OSType.LINUX, 063 true), // Jetson Nano, Jetson TX2 064 065 // PhotonVision Supported (Manual build/install) 066 LINUX_ARM64( 067 "Linux ARM64", Platform::getLinuxDeviceTreeModel, false, OSType.LINUX, true), // ODROID C2, N2 068 069 // Completely unsupported 070 WINDOWS_32("Windows x86", Platform::getUnknownModel, false, OSType.WINDOWS, false), 071 MACOS("Mac OS", Platform::getUnknownModel, false, OSType.MACOS, false), 072 LINUX_ARM32( 073 "Linux ARM32", Platform::getUnknownModel, false, OSType.LINUX, false), // ODROID XU4, C1+ 074 UNKNOWN("Unsupported Platform", Platform::getUnknownModel, false, OSType.UNKNOWN, false); 075 076 public enum OSType { 077 WINDOWS, 078 LINUX, 079 MACOS, 080 UNKNOWN 081 } 082 083 public final String description; 084 public final String hardwareModel; 085 public final boolean isPi; 086 public final OSType osType; 087 public final boolean isSupported; 088 089 // Set once at init, shouldn't be needed after. 090 private static Platform currentPlatform = getCurrentPlatform(); 091 private static boolean override = false; 092 093 Platform( 094 String description, 095 Supplier<String> getHardwareModel, 096 boolean isPi, 097 OSType osType, 098 boolean isSupported) { 099 this.description = description; 100 this.hardwareModel = getHardwareModel.get(); 101 this.isPi = isPi; 102 this.osType = osType; 103 this.isSupported = isSupported; 104 } 105 106 public static void overridePlatform(Platform platform) { 107 currentPlatform = platform; 108 override = true; 109 } 110 111 ////////////////////////////////////////////////////// 112 // Public API 113 114 // Checks specifically if unix shell and API are supported 115 public static boolean isLinux() { 116 return currentPlatform.osType == OSType.LINUX; 117 } 118 119 public static boolean isRK3588() { 120 return currentPlatform == LINUX_RK3588_64 121 || Platform.isOrangePi() 122 || Platform.isCoolPi4b() 123 || Platform.isRock5C() 124 || fileHasText("/proc/device-tree/compatible", "rk3588"); 125 } 126 127 public static boolean isQCS6490() { 128 return currentPlatform == LINUX_QCS6490 || Platform.isRubik(); 129 } 130 131 public static boolean isRaspberryPi() { 132 return currentPlatform.isPi; 133 } 134 135 public static String getPlatformName() { 136 if (currentPlatform.equals(UNKNOWN)) { 137 return UnknownPlatformString; 138 } else { 139 return currentPlatform.description; 140 } 141 } 142 143 /** 144 * This function serves to map between formats used in the CombinedRuntimeLoader and the platform 145 * names used in the wpilib-tools-plugin. This is typically used for native libraries. 146 * 147 * @return String representing the platform in the format used by wpilib-tools-plugin, or an empty 148 * string if the platform is not recognized. 149 */ 150 public static String getNativePlatform() { 151 String platPath = CombinedRuntimeLoader.getPlatformPath(); 152 153 switch (platPath) { 154 case "/linux/x86-64/": 155 return "linuxx86-64"; 156 case "/windows/x86-64/": 157 return "winx86-64"; 158 case "/linux/arm64/": 159 return "linuxarm64"; 160 default: 161 return ""; 162 } 163 } 164 165 public static String getHardwareModel() { 166 return currentPlatform.hardwareModel; 167 } 168 169 public static boolean isSupported() { 170 return currentPlatform.isSupported; 171 } 172 173 public static boolean isAthena() { 174 File runRobotFile = new File("/usr/local/frc/bin/frcRunRobot.sh"); 175 return runRobotFile.exists(); 176 } 177 178 public static boolean isWindows() { 179 var p = getCurrentPlatform(); 180 return (p == WINDOWS_32 || p == WINDOWS_64); 181 } 182 183 ////////////////////////////////////////////////////// 184 185 // Debug info related to unknown platforms for debug help 186 private static final String OS_NAME = System.getProperty("os.name"); 187 private static final String OS_ARCH = System.getProperty("os.arch"); 188 private static final String UnknownPlatformString = 189 String.format("Unknown Platform. OS: %s, Architecture: %s", OS_NAME, OS_ARCH); 190 private static final String UnknownDeviceModelString = "Unknown"; 191 192 public static Platform getCurrentPlatform() { 193 if (override) { 194 return currentPlatform; 195 } 196 197 String OS_NAME; 198 if (Platform.OS_NAME != null) { 199 OS_NAME = Platform.OS_NAME; 200 } else { 201 OS_NAME = System.getProperty("os.name"); 202 } 203 204 String OS_ARCH; 205 if (Platform.OS_ARCH != null) { 206 OS_ARCH = Platform.OS_ARCH; 207 } else { 208 OS_ARCH = System.getProperty("os.arch"); 209 } 210 211 if (OS_NAME.startsWith("Windows")) { 212 if (OS_ARCH.equals("x86") || OS_ARCH.equals("i386")) { 213 return WINDOWS_32; 214 } else if (OS_ARCH.equals("amd64") || OS_ARCH.equals("x86_64")) { 215 return WINDOWS_64; 216 } else { 217 // please don't try this 218 return UNKNOWN; 219 } 220 } 221 222 if (OS_NAME.startsWith("Mac")) { 223 // TODO - once we have real support, this might have to be more granular 224 return MACOS; 225 } 226 227 if (OS_NAME.startsWith("Linux")) { 228 if (isPiSBC()) { 229 if (OS_ARCH.equals("arm") || OS_ARCH.equals("arm32")) { 230 return LINUX_RASPBIAN32; 231 } else if (OS_ARCH.equals("aarch64") || OS_ARCH.equals("arm64")) { 232 return LINUX_RASPBIAN64; 233 } else { 234 // Unknown/exotic installation 235 return UNKNOWN; 236 } 237 } else if (isJetsonSBC()) { 238 if (OS_ARCH.equals("aarch64") || OS_ARCH.equals("arm64")) { 239 // TODO - do we need to check OS version? 240 return LINUX_AARCH64; 241 } else { 242 // Unknown/exotic installation 243 return UNKNOWN; 244 } 245 } else if (OS_ARCH.equals("amd64") || OS_ARCH.equals("x86_64")) { 246 return LINUX_64; 247 } else if (OS_ARCH.equals("x86") || OS_ARCH.equals("i386")) { 248 return LINUX_32; 249 } else if (OS_ARCH.equals("aarch64") || OS_ARCH.equals("arm64")) { 250 // TODO - os detection needed? 251 if (isRK3588()) { 252 return LINUX_RK3588_64; 253 } else if (isQCS6490()) { 254 return LINUX_QCS6490; 255 256 } else { 257 return LINUX_AARCH64; 258 } 259 } else if (OS_ARCH.equals("arm") || OS_ARCH.equals("arm32")) { 260 return LINUX_ARM32; 261 } else { 262 // Unknown or otherwise unsupported platform 263 return Platform.UNKNOWN; 264 } 265 } 266 267 // If we fall through all the way to here, 268 return Platform.UNKNOWN; 269 } 270 271 // Check for various known SBC types 272 private static boolean isPiSBC() { 273 return fileHasText("/proc/cpuinfo", "Raspberry Pi"); 274 } 275 276 private static boolean isOrangePi() { 277 return fileHasText("/proc/device-tree/model", "Orange Pi 5"); 278 } 279 280 private static boolean isRock5C() { 281 return fileHasText("/proc/device-tree/model", "ROCK 5C"); 282 } 283 284 private static boolean isCoolPi4b() { 285 return fileHasText("/proc/device-tree/model", "CoolPi 4B"); 286 } 287 288 private static boolean isJetsonSBC() { 289 // https://forums.developer.nvidia.com/t/how-to-recognize-jetson-nano-device/146624 290 return fileHasText("/proc/device-tree/model", "NVIDIA Jetson"); 291 } 292 293 private static boolean isRubik() { 294 return fileHasText("/proc/device-tree/model", "RUBIK"); 295 } 296 297 static String getLinuxDeviceTreeModel() { 298 var deviceTreeModelPath = Paths.get("/proc/device-tree/model"); 299 try { 300 if (Files.exists(deviceTreeModelPath)) { 301 return Files.readString(deviceTreeModelPath).trim(); 302 } 303 } catch (Exception ex) { 304 return UnknownDeviceModelString; 305 } 306 return UnknownDeviceModelString; 307 } 308 309 static String getUnknownModel() { 310 return UnknownDeviceModelString; 311 } 312 313 // Checks for various names of linux OS 314 private static boolean isStretch() { 315 // TODO - this is a total guess 316 return fileHasText("/etc/os-release", "Stretch"); 317 } 318 319 private static boolean isBuster() { 320 // TODO - this is a total guess 321 return fileHasText("/etc/os-release", "Buster"); 322 } 323 324 private static boolean fileHasText(String filename, String text) { 325 try (BufferedReader reader = Files.newBufferedReader(Paths.get(filename))) { 326 while (true) { 327 String value = reader.readLine(); 328 if (value == null) { 329 return false; 330 331 } else if (value.contains(text)) { 332 return true; 333 } // else, next line 334 } 335 } catch (IOException ex) { 336 return false; 337 } 338 } 339}