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