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