View Javadoc
1   /*
2    * Copyright 2016-2024 The OSHI Project Contributors
3    * SPDX-License-Identifier: MIT
4    */
5   package oshi.hardware.platform.linux;
6   
7   import static oshi.software.os.linux.LinuxOperatingSystem.HAS_UDEV;
8   
9   import java.io.File;
10  import java.time.LocalDate;
11  import java.util.ArrayList;
12  import java.util.Collections;
13  import java.util.HashMap;
14  import java.util.List;
15  import java.util.Map;
16  
17  import com.sun.jna.platform.linux.Udev;
18  import com.sun.jna.platform.linux.Udev.UdevContext;
19  import com.sun.jna.platform.linux.Udev.UdevDevice;
20  import com.sun.jna.platform.linux.Udev.UdevEnumerate;
21  import com.sun.jna.platform.linux.Udev.UdevListEntry;
22  
23  import oshi.annotation.concurrent.ThreadSafe;
24  import oshi.hardware.PowerSource;
25  import oshi.hardware.common.AbstractPowerSource;
26  import oshi.util.Constants;
27  import oshi.util.FileUtil;
28  import oshi.util.ParseUtil;
29  import oshi.util.platform.linux.SysPath;
30  
31  /**
32   * A Power Source
33   */
34  @ThreadSafe
35  public final class LinuxPowerSource extends AbstractPowerSource {
36  
37      public LinuxPowerSource(String psName, String psDeviceName, double psRemainingCapacityPercent,
38              double psTimeRemainingEstimated, double psTimeRemainingInstant, double psPowerUsageRate, double psVoltage,
39              double psAmperage, boolean psPowerOnLine, boolean psCharging, boolean psDischarging,
40              CapacityUnits psCapacityUnits, int psCurrentCapacity, int psMaxCapacity, int psDesignCapacity,
41              int psCycleCount, String psChemistry, LocalDate psManufactureDate, String psManufacturer,
42              String psSerialNumber, double psTemperature) {
43          super(psName, psDeviceName, psRemainingCapacityPercent, psTimeRemainingEstimated, psTimeRemainingInstant,
44                  psPowerUsageRate, psVoltage, psAmperage, psPowerOnLine, psCharging, psDischarging, psCapacityUnits,
45                  psCurrentCapacity, psMaxCapacity, psDesignCapacity, psCycleCount, psChemistry, psManufactureDate,
46                  psManufacturer, psSerialNumber, psTemperature);
47      }
48  
49      /**
50       * Gets Battery Information
51       *
52       * @return An array of PowerSource objects representing batteries, etc.
53       */
54      public static List<PowerSource> getPowerSources() {
55          String psName;
56          String psDeviceName;
57          double psRemainingCapacityPercent = -1d;
58          double psTimeRemainingEstimated = -1d; // -1 = unknown, -2 = unlimited
59          double psTimeRemainingInstant = -1d;
60          double psPowerUsageRate = 0d;
61          double psVoltage = -1d;
62          double psAmperage = 0d;
63          boolean psPowerOnLine = false;
64          boolean psCharging = false;
65          boolean psDischarging = false;
66          CapacityUnits psCapacityUnits = CapacityUnits.RELATIVE;
67          int psCurrentCapacity = -1;
68          int psMaxCapacity = -1;
69          int psDesignCapacity = -1;
70          int psCycleCount = -1;
71          String psChemistry;
72          LocalDate psManufactureDate = null;
73          String psManufacturer;
74          String psSerialNumber;
75          double psTemperature = 0d;
76  
77          List<PowerSource> psList = new ArrayList<>();
78          if (HAS_UDEV) {
79              UdevContext udev = Udev.INSTANCE.udev_new();
80              try {
81                  UdevEnumerate enumerate = udev.enumerateNew();
82                  try {
83                      enumerate.addMatchSubsystem("power_supply");
84                      enumerate.scanDevices();
85                      for (UdevListEntry entry = enumerate.getListEntry(); entry != null; entry = entry.getNext()) {
86                          String syspath = entry.getName();
87                          String name = syspath.substring(syspath.lastIndexOf(File.separatorChar) + 1);
88                          if (!name.startsWith("ADP") && !name.startsWith("AC")) {
89                              UdevDevice device = udev.deviceNewFromSyspath(syspath);
90                              if (device != null) {
91                                  try {
92                                      if (ParseUtil.parseIntOrDefault(device.getPropertyValue("POWER_SUPPLY_PRESENT"),
93                                              1) > 0
94                                              && ParseUtil.parseIntOrDefault(
95                                                      device.getPropertyValue("POWER_SUPPLY_ONLINE"), 1) > 0) {
96                                          psName = getOrDefault(device, "POWER_SUPPLY_NAME", name);
97                                          String status = device.getPropertyValue("POWER_SUPPLY_STATUS");
98                                          psCharging = "Charging".equals(status);
99                                          psDischarging = "Discharging".equals(status);
100                                         psRemainingCapacityPercent = ParseUtil.parseIntOrDefault(
101                                                 device.getPropertyValue("POWER_SUPPLY_CAPACITY"), -100) / 100d;
102 
103                                         // Debian/Ubuntu provides Energy. Fedora/RHEL provides Charge.
104                                         psCurrentCapacity = ParseUtil.parseIntOrDefault(
105                                                 device.getPropertyValue("POWER_SUPPLY_ENERGY_NOW"), -1);
106                                         if (psCurrentCapacity < 0) {
107                                             psCurrentCapacity = ParseUtil.parseIntOrDefault(
108                                                     device.getPropertyValue("POWER_SUPPLY_CHARGE_NOW"), -1);
109                                         }
110                                         psMaxCapacity = ParseUtil.parseIntOrDefault(
111                                                 device.getPropertyValue("POWER_SUPPLY_ENERGY_FULL"), 1);
112                                         if (psMaxCapacity < 0) {
113                                             psMaxCapacity = ParseUtil.parseIntOrDefault(
114                                                     device.getPropertyValue("POWER_SUPPLY_CHARGE_FULL"), 1);
115                                         }
116                                         psDesignCapacity = ParseUtil.parseIntOrDefault(
117                                                 device.getPropertyValue("POWER_SUPPLY_ENERGY_FULL_DESIGN"), 1);
118                                         if (psDesignCapacity < 0) {
119                                             psDesignCapacity = ParseUtil.parseIntOrDefault(
120                                                     device.getPropertyValue("POWER_SUPPLY_CHARGE_FULL_DESIGN"), 1);
121                                         }
122 
123                                         // Debian/Ubuntu provides Voltage and Power.
124                                         // Fedora/RHEL provides Voltage and Current.
125                                         psVoltage = ParseUtil.parseIntOrDefault(
126                                                 device.getPropertyValue("POWER_SUPPLY_VOLTAGE_NOW"), -1);
127                                         // From Physics we know P = IV so I = P/V
128                                         if (psVoltage > 0) {
129                                             String power = device.getPropertyValue("POWER_SUPPLY_POWER_NOW");
130                                             String current = device.getPropertyValue("POWER_SUPPLY_CURRENT_NOW");
131                                             if (power == null) {
132                                                 psAmperage = ParseUtil.parseIntOrDefault(current, 0);
133                                                 psPowerUsageRate = psAmperage * psVoltage;
134                                             } else if (current == null) {
135                                                 psPowerUsageRate = ParseUtil.parseIntOrDefault(power, 0);
136                                                 psAmperage = psPowerUsageRate / psVoltage;
137                                             } else {
138                                                 psAmperage = ParseUtil.parseIntOrDefault(current, 0);
139                                                 psPowerUsageRate = ParseUtil.parseIntOrDefault(power, 0);
140                                             }
141                                         }
142 
143                                         psCycleCount = ParseUtil.parseIntOrDefault(
144                                                 device.getPropertyValue("POWER_SUPPLY_CYCLE_COUNT"), -1);
145                                         psChemistry = getOrDefault(device, "POWER_SUPPLY_TECHNOLOGY",
146                                                 Constants.UNKNOWN);
147                                         psDeviceName = getOrDefault(device, "POWER_SUPPLY_MODEL_NAME",
148                                                 Constants.UNKNOWN);
149                                         psManufacturer = getOrDefault(device, "POWER_SUPPLY_MANUFACTURER",
150                                                 Constants.UNKNOWN);
151                                         psSerialNumber = getOrDefault(device, "POWER_SUPPLY_SERIAL_NUMBER",
152                                                 Constants.UNKNOWN);
153 
154                                         psList.add(new LinuxPowerSource(psName, psDeviceName,
155                                                 psRemainingCapacityPercent, psTimeRemainingEstimated,
156                                                 psTimeRemainingInstant, psPowerUsageRate, psVoltage, psAmperage,
157                                                 psPowerOnLine, psCharging, psDischarging, psCapacityUnits,
158                                                 psCurrentCapacity, psMaxCapacity, psDesignCapacity, psCycleCount,
159                                                 psChemistry, psManufactureDate, psManufacturer, psSerialNumber,
160                                                 psTemperature));
161                                     }
162                                 } finally {
163                                     device.unref();
164                                 }
165                             }
166                         }
167                     }
168                 } finally {
169                     enumerate.unref();
170                 }
171             } finally {
172                 udev.unref();
173             }
174         } else {
175             File psDir = new File(SysPath.POWER_SUPPLY);
176             File[] psArr = psDir.listFiles();
177             if (psArr == null) {
178                 return Collections.emptyList();
179             }
180             for (File ps : psArr) {
181                 String name = ps.getName();
182                 if (!name.startsWith("ADP") && !name.startsWith("AC")) {
183                     // Skip if can't read uevent file
184                     List<String> psInfo = FileUtil.readFile(SysPath.POWER_SUPPLY + "/" + name + "/uevent", false);
185                     Map<String, String> psMap = new HashMap<>();
186                     for (String line : psInfo) {
187                         String[] split = line.split("=");
188                         if (split.length > 1 && !split[1].isEmpty()) {
189                             psMap.put(split[0], split[1]);
190                         }
191                     }
192 
193                     psName = psMap.getOrDefault("POWER_SUPPLY_NAME", name);
194                     String status = psMap.get("POWER_SUPPLY_STATUS");
195                     psCharging = "Charging".equals(status);
196                     psDischarging = "Discharging".equals(status);
197 
198                     if (psMap.containsKey("POWER_SUPPLY_CAPACITY")) {
199                         psRemainingCapacityPercent = ParseUtil.parseIntOrDefault(psMap.get("POWER_SUPPLY_CAPACITY"),
200                                 -100) / 100d;
201                     }
202                     if (psMap.containsKey("POWER_SUPPLY_ENERGY_NOW")) {
203                         psCurrentCapacity = ParseUtil.parseIntOrDefault(psMap.get("POWER_SUPPLY_ENERGY_NOW"), -1);
204                     } else if (psMap.containsKey("POWER_SUPPLY_CHARGE_NOW")) {
205                         psCurrentCapacity = ParseUtil.parseIntOrDefault(psMap.get("POWER_SUPPLY_CHARGE_NOW"), -1);
206                     }
207                     if (psMap.containsKey("POWER_SUPPLY_ENERGY_FULL")) {
208                         psCurrentCapacity = ParseUtil.parseIntOrDefault(psMap.get("POWER_SUPPLY_ENERGY_FULL"), 1);
209                     } else if (psMap.containsKey("POWER_SUPPLY_CHARGE_FULL")) {
210                         psCurrentCapacity = ParseUtil.parseIntOrDefault(psMap.get("POWER_SUPPLY_CHARGE_FULL"), 1);
211                     }
212                     if (psMap.containsKey("POWER_SUPPLY_ENERGY_FULL_DESIGN")) {
213                         psMaxCapacity = ParseUtil.parseIntOrDefault(psMap.get("POWER_SUPPLY_ENERGY_FULL_DESIGN"), 1);
214                     } else if (psMap.containsKey("POWER_SUPPLY_CHARGE_FULL_DESIGN")) {
215                         psMaxCapacity = ParseUtil.parseIntOrDefault(psMap.get("POWER_SUPPLY_CHARGE_FULL_DESIGN"), 1);
216                     }
217                     // Debian/Ubuntu provides Voltage and Power.
218                     // Fedora/RHEL provides Voltage and Current.
219                     if (psMap.containsKey("POWER_SUPPLY_VOLTAGE_NOW")) {
220                         psVoltage = ParseUtil.parseIntOrDefault(psMap.get("POWER_SUPPLY_VOLTAGE_NOW"), -1);
221                     }
222                     // From Physics we know P = IV so I = P/V
223                     if (psVoltage > 0) {
224                         if (psMap.containsKey("POWER_SUPPLY_POWER_NOW")) {
225                             psPowerUsageRate = ParseUtil.parseIntOrDefault(psMap.get("POWER_SUPPLY_POWER_NOW"), -1);
226                         }
227                         if (psMap.containsKey("POWER_SUPPLY_CURRENT_NOW")) {
228                             psAmperage = ParseUtil.parseIntOrDefault(psMap.get("POWER_SUPPLY_CURRENT_NOW"), -1);
229                         }
230                         if (psPowerUsageRate < 0 && psAmperage >= 0) {
231                             psPowerUsageRate = psAmperage * psVoltage;
232                         } else if (psPowerUsageRate >= 0 && psAmperage < 0) {
233                             psAmperage = psPowerUsageRate / psVoltage;
234                         } else {
235                             psAmperage = 0;
236                             psPowerUsageRate = 0;
237                         }
238                     }
239 
240                     if (psMap.containsKey("POWER_SUPPLY_CYCLE_COUNT")) {
241                         psCycleCount = ParseUtil.parseIntOrDefault(psMap.get("POWER_SUPPLY_CYCLE_COUNT"), -1);
242                     }
243                     psChemistry = psMap.getOrDefault("POWER_SUPPLY_TECHNOLOGY", Constants.UNKNOWN);
244                     psDeviceName = psMap.getOrDefault("POWER_SUPPLY_MODEL_NAME", Constants.UNKNOWN);
245                     psManufacturer = psMap.getOrDefault("POWER_SUPPLY_MANUFACTURER", Constants.UNKNOWN);
246                     psSerialNumber = psMap.getOrDefault("POWER_SUPPLY_SERIAL_NUMBER", Constants.UNKNOWN);
247                     if (ParseUtil.parseIntOrDefault(psMap.get("POWER_SUPPLY_PRESENT"), 1) > 0) {
248                         psList.add(new LinuxPowerSource(psName, psDeviceName, psRemainingCapacityPercent,
249                                 psTimeRemainingEstimated, psTimeRemainingInstant, psPowerUsageRate, psVoltage,
250                                 psAmperage, psPowerOnLine, psCharging, psDischarging, psCapacityUnits,
251                                 psCurrentCapacity, psMaxCapacity, psDesignCapacity, psCycleCount, psChemistry,
252                                 psManufactureDate, psManufacturer, psSerialNumber, psTemperature));
253                     }
254                 }
255             }
256         }
257         return psList;
258     }
259 
260     private static String getOrDefault(UdevDevice device, String property, String def) {
261         String value = device.getPropertyValue(property);
262         return value == null ? def : value;
263     }
264 }