001package com.pi4j.system;
002
003/*
004 * #%L
005 * **********************************************************************
006 * ORGANIZATION  :  Pi4J
007 * PROJECT       :  Pi4J :: Java Library (Core)
008 * FILENAME      :  SystemInfo.java  
009 * 
010 * This file is part of the Pi4J project. More information about 
011 * this project can be found here:  http://www.pi4j.com/
012 * **********************************************************************
013 * %%
014 * Copyright (C) 2012 - 2015 Pi4J
015 * %%
016 * This program is free software: you can redistribute it and/or modify
017 * it under the terms of the GNU Lesser General Public License as
018 * published by the Free Software Foundation, either version 3 of the
019 * License, or (at your option) any later version.
020 * 
021 * This program is distributed in the hope that it will be useful,
022 * but WITHOUT ANY WARRANTY; without even the implied warranty of
023 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
024 * GNU General Lesser Public License for more details.
025 * 
026 * You should have received a copy of the GNU General Lesser Public
027 * License along with this program.  If not, see
028 * <http://www.gnu.org/licenses/lgpl-3.0.html>.
029 * #L%
030 */
031
032
033import com.pi4j.util.ExecUtil;
034import com.pi4j.util.StringUtil;
035
036import java.io.BufferedReader;
037import java.io.FileReader;
038import java.io.IOException;
039import java.security.AccessController;
040import java.security.PrivilegedAction;
041import java.text.ParseException;
042import java.util.ArrayList;
043import java.util.HashMap;
044import java.util.List;
045import java.util.Map;
046
047public class SystemInfo {
048
049    // private constructor 
050    private SystemInfo() {
051        // forbid object construction 
052    }
053    
054    public enum BoardType {
055        UNKNOWN,
056        ModelA_Rev1,
057        ModelA_Plus_Rev1,
058        ModelB_Rev1,
059        ModelB_Rev2,
060        ModelB_Plus_Rev1,
061        Compute_Module_Rev1,
062        Model2B_Rev1
063    }    
064    
065    private static Map<String, String> cpuInfo;
066
067    private static String getCpuInfo(String target) throws IOException, InterruptedException {
068        // if the CPU data has not been previously acquired, then acquire it now
069        if (cpuInfo == null) {
070            cpuInfo = new HashMap<>();
071
072            try(BufferedReader br = new BufferedReader(new FileReader("/proc/cpuinfo"))) {
073                for(String line; (line = br.readLine()) != null; ) {
074                    String parts[] = line.split(":", 2);
075                    if (parts.length >= 2 && !parts[0].trim().isEmpty() && !parts[1].trim().isEmpty()) {
076                        String cpuKey = parts[0].trim().toLowerCase();
077                        cpuInfo.put(cpuKey, parts[1].trim());
078                    }
079                }
080            }
081        }
082
083        target = target.toLowerCase();
084        if (cpuInfo.containsKey(target)) {
085            return cpuInfo.get(target);
086        }
087
088        throw new RuntimeException("Invalid target: " + target);
089    }
090
091    public static String getProcessor()  throws IOException, InterruptedException {
092        return getCpuInfo("processor");
093    }
094
095    public static String getModelName() throws IOException, InterruptedException {
096        return getCpuInfo("model name");
097    }
098
099    @Deprecated
100    public static String getBogoMIPS() throws IOException, InterruptedException {
101        return "UNKNOWN";
102    }
103
104    public static String[] getCpuFeatures() throws IOException, InterruptedException {
105        return getCpuInfo("Features").split(" ");
106    }
107
108    public static String getCpuImplementer() throws IOException, InterruptedException {
109        return getCpuInfo("CPU implementer");
110    }
111
112    public static String getCpuArchitecture() throws IOException, InterruptedException {
113        return getCpuInfo("CPU architecture");
114    }
115
116    public static String getCpuVariant() throws IOException, InterruptedException {
117        return getCpuInfo("CPU variant");
118    }
119
120    public static String getCpuPart() throws IOException, InterruptedException {
121        return getCpuInfo("CPU part");
122    }
123
124    public static String getCpuRevision() throws IOException, InterruptedException {
125        return getCpuInfo("CPU revision");
126    }
127
128    public static String getHardware() throws IOException, InterruptedException {
129        return getCpuInfo("Hardware");
130    }
131
132    public static String getRevision() throws IOException, InterruptedException {
133        return getCpuInfo("Revision");
134    }
135
136    public static String getSerial() throws IOException, InterruptedException {
137        return getCpuInfo("Serial");
138    }
139
140    public static String getOsName() {
141        return System.getProperty("os.name");
142    }
143
144    public static String getOsVersion() {
145        return System.getProperty("os.version");
146    }
147
148    public static String getOsArch()  {
149        return System.getProperty("os.arch");
150    }
151    
152    public static String getOsFirmwareBuild() throws IOException, InterruptedException {
153        String result[] = ExecUtil.execute("/opt/vc/bin/vcgencmd version");
154        if(result != null){
155            for(String line : result) {
156                if(line.startsWith("version ")){                
157                    return line.substring(8);
158                }
159            }
160        }
161        throw new RuntimeException("Invalid command or response.");
162    }    
163
164    public static String getOsFirmwareDate() throws IOException, InterruptedException, ParseException {
165        String result[] = ExecUtil.execute("/opt/vc/bin/vcgencmd version");
166        if(result != null && result.length > 0){
167            for(String line : result) {
168                return line; // return 1st line
169            }
170        }
171        throw new RuntimeException("Invalid command or response.");
172    }    
173    
174    public static String getJavaVendor()  {
175        return System.getProperty("java.vendor");
176    }
177 
178    public static String getJavaVendorUrl() {
179        return System.getProperty("java.vendor.url");
180    }
181 
182    public static String getJavaVersion() {
183        return System.getProperty("java.version");
184    }
185
186    public static String getJavaVirtualMachine() {
187        return System.getProperty("java.vm.name");
188    }
189
190    public static String getJavaRuntime() {
191        return AccessController.doPrivileged(new PrivilegedAction<String>()  {
192            public String run()  {
193                return System.getProperty("java.runtime.name");
194            }
195        });
196    }
197    
198    /*
199     * this method was partially derived from :: (project) jogamp / (developer) sgothel
200     * https://github.com/sgothel/gluegen/blob/master/src/java/jogamp/common/os/PlatformPropsImpl.java#L160
201     * https://github.com/sgothel/gluegen/blob/master/LICENSE.txt
202     */
203    public static boolean isHardFloatAbi() {
204        
205        return AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
206            private final String[] gnueabihf = new String[] { "gnueabihf", "armhf" };
207            public Boolean run() {                    
208                return ( StringUtil.contains(System.getProperty("sun.boot.library.path"), gnueabihf) ||
209                     StringUtil.contains(System.getProperty("java.library.path"), gnueabihf) ||
210                     StringUtil.contains(System.getProperty("java.home"), gnueabihf) || 
211                     getBashVersionInfo().contains("gnueabihf") ||
212                     hasReadElfTag("Tag_ABI_HardFP_use"));
213            } } );
214    }
215    
216
217    private static List<Long> getMemory() throws IOException, InterruptedException {
218        // Memory information is in the form
219        // root@mypi:/home/pi# free -b
220        //              total       used       free     shared    buffers     cached
221        // Mem:     459771904  144654336  315117568          0   21319680   63713280
222        // -/+ buffers/cache:   59621376  400150528
223        // Swap:    104853504          0  104853504
224        List<Long> values = new ArrayList<>();
225        String result[] = ExecUtil.execute("free -b");
226        if(result != null){
227            for(String line : result) {
228                if(line.startsWith("Mem:")){
229                    String parts[] = line.split(" ");                    
230                    for(String part : parts){
231                        part = part.trim();
232                        if(!part.isEmpty() && !part.equalsIgnoreCase("Mem:")){
233                            values.add(new Long(part));
234                        }
235                    }
236                }
237            }
238        }
239        return values;
240    }
241    
242    public static long getMemoryTotal() throws IOException, InterruptedException {
243        List<Long> values = getMemory();
244        if(!values.isEmpty() && values.size() > 0){
245            return values.get(0); // total memory value is in first position
246        }
247        return -1;
248    }
249
250    public static long getMemoryUsed() throws IOException, InterruptedException {
251        List<Long> values = getMemory();
252        if(!values.isEmpty() && values.size() > 1){
253            return values.get(1); // used memory value is in second position
254        }
255        return -1;
256    }
257
258    public static long getMemoryFree() throws IOException, InterruptedException {
259        List<Long> values = getMemory();
260        if(!values.isEmpty() && values.size() > 2){
261            return values.get(2); // free memory value is in third position
262        }
263        return -1;
264    }
265
266    public static long getMemoryShared() throws IOException, InterruptedException {
267        List<Long> values = getMemory();
268        if(!values.isEmpty() && values.size() > 3){
269            return values.get(3); // shared memory value is in fourth position
270        }
271        return -1;
272    }
273
274    public static long getMemoryBuffers() throws IOException, InterruptedException {
275        List<Long> values = getMemory();
276        if(!values.isEmpty() && values.size() > 4){
277            return values.get(4); // buffers memory value is in fifth position
278        }
279        return -1;
280    }
281
282    public static long getMemoryCached() throws IOException, InterruptedException {
283        List<Long> values = getMemory();
284        if(!values.isEmpty() && values.size() > 5){
285            return values.get(5); // cached memory value is in sixth position
286        }
287        return -1;
288    }
289
290    public static BoardType getBoardType() throws IOException, InterruptedException
291    {
292        String revision = getRevision();
293        long irevision = Long.parseLong(revision, 16);
294        long scheme = (irevision >> 23) & 0x1;
295        long ram = (irevision >> 20) & 0x7;
296        long manufacturer = (irevision >> 16) & 0xF;
297        long processor = (irevision >> 12) & 0xF;
298        long type = (irevision >> 4) & 0xFF;
299        long rev = irevision & 0xF;
300
301        // determine board type based on revision scheme
302        if (scheme == 0) {
303            // The following info obtained from:
304            // http://www.raspberrypi.org/archives/1929
305            // http://raspberryalphaomega.org.uk/?p=428
306            // http://www.raspberrypi.org/phpBB3/viewtopic.php?p=281039#p281039
307            // http://elinux.org/RPi_HardwareHistory
308            switch (revision) {
309                case "0002":  // Model B Revision 1
310                case "0003":  // Model B Revision 1 + Fuses mod and D14 removed
311                    return BoardType.ModelB_Rev1;
312                case "0004":  // Model B Revision 2 256MB (Sony)
313                case "0005":  // Model B Revision 2 256MB (Qisda)
314                case "0006":  // Model B Revision 2 256MB (Egoman)
315                    return BoardType.ModelB_Rev2;
316                case "0007":  // Model A 256MB (Egoman)
317                case "0008":  // Model A 256MB (Sony)
318                case "0009":  // Model A 256MB (Qisda)
319                    return BoardType.ModelA_Rev1;
320                case "000d":  // Model B Revision 2 512MB (Egoman)
321                case "000e":  // Model B Revision 2 512MB (Sony)
322                case "000f":  // Model B Revision 2 512MB (Qisda)
323                    return BoardType.ModelB_Rev2;
324                case "0010":  // Model B Plus 512MB (Sony)
325                {             // Model 2B, Rev 1.1, Quad Core, 1GB (Sony)
326                    if (getHardware().equalsIgnoreCase("BCM2709"))
327                        return BoardType.Model2B_Rev1;
328                    else
329                        return BoardType.ModelB_Plus_Rev1;
330                }
331                case "0011":  // Compute Module 512MB (Sony)
332                    return BoardType.Compute_Module_Rev1;
333                case "0012":  // Model A Plus 512MB (Sony)
334                    return BoardType.ModelA_Plus_Rev1;
335                default:
336                    return BoardType.UNKNOWN;
337            }
338        }
339        else if (type == 4) {
340            return SystemInfo.BoardType.Model2B_Rev1;
341        }
342        else {
343            return SystemInfo.BoardType.UNKNOWN;
344        }
345    }
346
347    public static float getCpuTemperature() throws IOException, InterruptedException, NumberFormatException {
348        // CPU temperature is in the form
349        // pi@mypi$ /opt/vc/bin/vcgencmd measure_temp
350        // temp=42.3'C
351        // Support for this was added around firmware version 3357xx per info
352        // at http://www.raspberrypi.org/phpBB3/viewtopic.php?p=169909#p169909
353        String result[] = ExecUtil.execute("/opt/vc/bin/vcgencmd measure_temp");
354        if(result != null && result.length > 0){
355            for(String line : result) {
356                String parts[] = line.split("[=']", 3);                
357                return Float.parseFloat(parts[1]);
358            }
359        }
360        throw new RuntimeException("Invalid command or response.");
361    }
362
363    private static float getVoltage(String id) throws IOException, InterruptedException, NumberFormatException {
364        String result[] = ExecUtil.execute("/opt/vc/bin/vcgencmd measure_volts " + id);
365        if(result != null && result.length > 0){
366            for(String line : result) {
367                String parts[] = line.split("[=V]", 3);                
368                return Float.parseFloat(parts[1]);
369            }
370        }
371        throw new RuntimeException("Invalid command or response.");
372    }
373    
374    public static float getCpuVoltage() throws IOException, InterruptedException, NumberFormatException {
375        return getVoltage("core");
376    }
377
378    public static float getMemoryVoltageSDRam_C() throws IOException, InterruptedException, NumberFormatException {
379        return getVoltage("sdram_c");
380    }
381
382    public static float getMemoryVoltageSDRam_I() throws IOException, InterruptedException, NumberFormatException {
383        return getVoltage("sdram_i");
384    }
385
386    public static float getMemoryVoltageSDRam_P() throws IOException, InterruptedException, NumberFormatException {
387        return getVoltage("sdram_p");
388    }
389    
390
391    private static boolean getCodecEnabled(String codec) throws IOException, InterruptedException {
392        String result[] = ExecUtil.execute("/opt/vc/bin/vcgencmd codec_enabled " + codec);
393        if(result != null && result.length > 0){
394            for(String line : result) {
395                String parts[] = line.split("=", 2);                
396                return parts[1].trim().equalsIgnoreCase("enabled");
397            }
398        }
399        throw new RuntimeException("Invalid command or response.");
400    }
401    
402    public static boolean getCodecH264Enabled() throws IOException, InterruptedException {
403        return getCodecEnabled("H264");
404    }
405
406    public static boolean getCodecMPG2Enabled() throws IOException, InterruptedException {
407        return getCodecEnabled("MPG2");
408    }
409
410    public static boolean getCodecWVC1Enabled() throws IOException, InterruptedException {
411        return getCodecEnabled("WVC1");
412    }
413    
414
415    private static long getClockFrequency(String target) throws IOException, InterruptedException {
416        String result[] = ExecUtil.execute("/opt/vc/bin/vcgencmd measure_clock " + target.trim());
417        if(result != null && result.length > 0){
418            for(String line : result) {
419                String parts[] = line.split("=", 2);                
420                return Long.parseLong(parts[1].trim());
421            }
422        }
423        throw new RuntimeException("Invalid command or response.");
424    }
425    
426    public static long getClockFrequencyArm() throws IOException, InterruptedException {
427        return getClockFrequency("arm");
428    }
429
430    public static long getClockFrequencyCore() throws IOException, InterruptedException {
431        return getClockFrequency("core");
432    }
433
434    public static long getClockFrequencyH264() throws IOException, InterruptedException {
435        return getClockFrequency("h264");
436    }
437
438    public static long getClockFrequencyISP() throws IOException, InterruptedException {
439        return getClockFrequency("isp");
440    }
441
442    public static long getClockFrequencyV3D() throws IOException, InterruptedException {
443        return getClockFrequency("v3d");
444    }
445
446    public static long getClockFrequencyUART() throws IOException, InterruptedException {
447        return getClockFrequency("uart");
448    }
449
450    public static long getClockFrequencyPWM() throws IOException, InterruptedException {
451        return getClockFrequency("pwm");
452    }
453
454    public static long getClockFrequencyEMMC() throws IOException, InterruptedException {
455        return getClockFrequency("emmc");
456    }
457    
458    public static long getClockFrequencyPixel() throws IOException, InterruptedException {
459        return getClockFrequency("pixel");
460    }
461    
462    public static long getClockFrequencyVEC() throws IOException, InterruptedException {
463        return getClockFrequency("vec");
464    }
465    
466    public static long getClockFrequencyHDMI() throws IOException, InterruptedException {
467        return getClockFrequency("hdmi");
468    }
469
470    public static long getClockFrequencyDPI() throws IOException, InterruptedException {
471        return getClockFrequency("dpi");
472    }
473    
474    
475    /*
476     * this method will to obtain the version info string from the 'bash' program
477     * (this method is used to help determine the HARD-FLOAT / SOFT-FLOAT ABI of the system)
478     */
479    private static String getBashVersionInfo() {
480        String versionInfo = "";
481        try {
482            String result[] = ExecUtil.execute("bash --version");
483            for(String line : result) {
484                if(!line.isEmpty()) { 
485                    versionInfo = line; // return only first output line of version info
486                    break;
487                }
488            }
489        }
490        catch (IOException|InterruptedException ioe) { ioe.printStackTrace(); }
491        return versionInfo;
492    }
493
494    /*
495     * this method will determine if a specified tag exists from the elf info in the '/proc/self/exe' program
496     * (this method is used to help determine the HARD-FLOAT / SOFT-FLOAT ABI of the system)
497     */    
498    private static boolean hasReadElfTag(String tag) {
499        String tagValue = getReadElfTag(tag);
500        return (tagValue != null && !tagValue.isEmpty());
501    }
502    
503    /*
504     * this method will obtain a specified tag value from the elf info in the '/proc/self/exe' program
505     * (this method is used to help determine the HARD-FLOAT / SOFT-FLOAT ABI of the system)
506     */    
507    private static String getReadElfTag(String tag) {
508        String tagValue = null;
509        try {
510            String result[] = ExecUtil.execute("/usr/bin/readelf -A /proc/self/exe");
511            if(result != null){
512                for(String line : result) {
513                    line = line.trim();
514                    if (line.startsWith(tag) && line.contains(":")) {
515                        String lineParts[] = line.split(":", 2);
516                        if(lineParts.length > 1)
517                            tagValue = lineParts[1].trim();
518                        break;
519                    }
520                }
521            }            
522        }
523        catch (IOException|InterruptedException ioe) { ioe.printStackTrace(); }
524        return tagValue;
525    }
526}