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