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 if (platPath == "/linux/x86-64/") { 154 return "linuxx64"; 155 } else if (platPath == "/windows/x86-64/") { 156 return "winx64"; 157 } else if (platPath == "/linux/arm64/") { 158 return "linuxarm64"; 159 } else { 160 return ""; 161 } 162 } 163 164 public static String getHardwareModel() { 165 return currentPlatform.hardwareModel; 166 } 167 168 public static boolean isSupported() { 169 return currentPlatform.isSupported; 170 } 171 172 public static boolean isAthena() { 173 File runRobotFile = new File("/usr/local/frc/bin/frcRunRobot.sh"); 174 return runRobotFile.exists(); 175 } 176 177 public static boolean isWindows() { 178 var p = getCurrentPlatform(); 179 return (p == WINDOWS_32 || p == WINDOWS_64); 180 } 181 182 ////////////////////////////////////////////////////// 183 184 // Debug info related to unknown platforms for debug help 185 private static final String OS_NAME = System.getProperty("os.name"); 186 private static final String OS_ARCH = System.getProperty("os.arch"); 187 private static final String UnknownPlatformString = 188 String.format("Unknown Platform. OS: %s, Architecture: %s", OS_NAME, OS_ARCH); 189 private static final String UnknownDeviceModelString = "Unknown"; 190 191 public static Platform getCurrentPlatform() { 192 if (override) { 193 return currentPlatform; 194 } 195 196 String OS_NAME; 197 if (Platform.OS_NAME != null) { 198 OS_NAME = Platform.OS_NAME; 199 } else { 200 OS_NAME = System.getProperty("os.name"); 201 } 202 203 String OS_ARCH; 204 if (Platform.OS_ARCH != null) { 205 OS_ARCH = Platform.OS_ARCH; 206 } else { 207 OS_ARCH = System.getProperty("os.arch"); 208 } 209 210 if (OS_NAME.startsWith("Windows")) { 211 if (OS_ARCH.equals("x86") || OS_ARCH.equals("i386")) { 212 return WINDOWS_32; 213 } else if (OS_ARCH.equals("amd64") || OS_ARCH.equals("x86_64")) { 214 return WINDOWS_64; 215 } else { 216 // please don't try this 217 return UNKNOWN; 218 } 219 } 220 221 if (OS_NAME.startsWith("Mac")) { 222 // TODO - once we have real support, this might have to be more granular 223 return MACOS; 224 } 225 226 if (OS_NAME.startsWith("Linux")) { 227 if (isPiSBC()) { 228 if (OS_ARCH.equals("arm") || OS_ARCH.equals("arm32")) { 229 return LINUX_RASPBIAN32; 230 } else if (OS_ARCH.equals("aarch64") || OS_ARCH.equals("arm64")) { 231 return LINUX_RASPBIAN64; 232 } else { 233 // Unknown/exotic installation 234 return UNKNOWN; 235 } 236 } else if (isJetsonSBC()) { 237 if (OS_ARCH.equals("aarch64") || OS_ARCH.equals("arm64")) { 238 // TODO - do we need to check OS version? 239 return LINUX_AARCH64; 240 } else { 241 // Unknown/exotic installation 242 return UNKNOWN; 243 } 244 } else if (OS_ARCH.equals("amd64") || OS_ARCH.equals("x86_64")) { 245 return LINUX_64; 246 } else if (OS_ARCH.equals("x86") || OS_ARCH.equals("i386")) { 247 return LINUX_32; 248 } else if (OS_ARCH.equals("aarch64") || OS_ARCH.equals("arm64")) { 249 // TODO - os detection needed? 250 if (isRK3588()) { 251 return LINUX_RK3588_64; 252 } else if (isQCS6490()) { 253 return LINUX_QCS6490; 254 255 } else { 256 return LINUX_AARCH64; 257 } 258 } else if (OS_ARCH.equals("arm") || OS_ARCH.equals("arm32")) { 259 return LINUX_ARM32; 260 } else { 261 // Unknown or otherwise unsupported platform 262 return Platform.UNKNOWN; 263 } 264 } 265 266 // If we fall through all the way to here, 267 return Platform.UNKNOWN; 268 } 269 270 // Check for various known SBC types 271 private static boolean isPiSBC() { 272 return fileHasText("/proc/cpuinfo", "Raspberry Pi"); 273 } 274 275 private static boolean isOrangePi() { 276 return fileHasText("/proc/device-tree/model", "Orange Pi 5"); 277 } 278 279 private static boolean isRock5C() { 280 return fileHasText("/proc/device-tree/model", "ROCK 5C"); 281 } 282 283 private static boolean isCoolPi4b() { 284 return fileHasText("/proc/device-tree/model", "CoolPi 4B"); 285 } 286 287 private static boolean isJetsonSBC() { 288 // https://forums.developer.nvidia.com/t/how-to-recognize-jetson-nano-device/146624 289 return fileHasText("/proc/device-tree/model", "NVIDIA Jetson"); 290 } 291 292 private static boolean isRubik() { 293 return fileHasText("/proc/device-tree/model", "RUBIK"); 294 } 295 296 static String getLinuxDeviceTreeModel() { 297 var deviceTreeModelPath = Paths.get("/proc/device-tree/model"); 298 try { 299 if (Files.exists(deviceTreeModelPath)) { 300 return Files.readString(deviceTreeModelPath).trim(); 301 } 302 } catch (Exception ex) { 303 return UnknownDeviceModelString; 304 } 305 return UnknownDeviceModelString; 306 } 307 308 static String getUnknownModel() { 309 return UnknownDeviceModelString; 310 } 311 312 // Checks for various names of linux OS 313 private static boolean isStretch() { 314 // TODO - this is a total guess 315 return fileHasText("/etc/os-release", "Stretch"); 316 } 317 318 private static boolean isBuster() { 319 // TODO - this is a total guess 320 return fileHasText("/etc/os-release", "Buster"); 321 } 322 323 private static boolean fileHasText(String filename, String text) { 324 try (BufferedReader reader = Files.newBufferedReader(Paths.get(filename))) { 325 while (true) { 326 String value = reader.readLine(); 327 if (value == null) { 328 return false; 329 330 } else if (value.contains(text)) { 331 return true; 332 } // else, next line 333 } 334 } catch (IOException ex) { 335 return false; 336 } 337 } 338}