diff --git a/CHANGELOG.md b/CHANGELOG.md index 0aa3216a919..436705d1964 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ 4.2.0 (in progress) ================ +* [#1038](https://github.com/oshi/oshi/pull/1038): More Battery Statistics. - [@dbwiddis](https://github.com/dbwiddis). * Your contribution here. 4.1.0 (10/16/2019), 4.1.1 (10/24/2019) diff --git a/mvnw.cmd b/mvnw.cmd old mode 100644 new mode 100755 diff --git a/oshi-core/src/main/java/oshi/hardware/PowerSource.java b/oshi-core/src/main/java/oshi/hardware/PowerSource.java index 36c35870fcf..0c6be1fa08d 100644 --- a/oshi-core/src/main/java/oshi/hardware/PowerSource.java +++ b/oshi-core/src/main/java/oshi/hardware/PowerSource.java @@ -23,35 +23,233 @@ */ package oshi.hardware; +import java.time.LocalDate; + /** * The Power Source is one or more batteries with some capacity, and some state * of charge/discharge */ public interface PowerSource { /** - * Name of the power source (e.g., InternalBattery-0) + * Units of Battery Capacity + */ + enum CapacityUnits { + /** + * MilliWattHours (mWh). + */ + MWH, + + /** + * MilliAmpHours (mAh). Should be multiplied by voltage to convert to mWh. + */ + MAH, + + /** + * Relative units. The specific units are not defined. The ratio of current/max + * capacity still represents state of charge and the ratio of max/design + * capacity still represents state of health. + */ + RELATIVE; + } + + /** + * Name of the power source at the Operating System level. * - * @return The power source name + * @return The power source name, as reported by the operating system. */ String getName(); /** - * Remaining capacity as a fraction of max capacity. + * Name of the power source at the device level. * - * @return A value between 0.0 (fully drained) and 1.0 (fully charged) + * @return The power source name, as reported by the device itself. + */ + String getDeviceName(); + + /** + * @deprecated Use {@link #getRemainingCapacityPercent()} */ + @Deprecated double getRemainingCapacity(); /** - * Estimated time remaining on the power source, in seconds. + * Estimated remaining capacity as a fraction of max capacity. + *

+ * This is an estimated/smoothed value which should correspond to the Operating + * System's "percent power" display, and may not directly correspond to the + * ratio of {@link #getCurrentCapacity()} to {@link #getMaxCapacity()}. + * + * @return A value between 0.0 (fully drained) and 1.0 (fully charged) + */ + double getRemainingCapacityPercent(); + + /** + * @deprecated Use {@link #getTimeRemainingEstimated()} + */ + @Deprecated + double getTimeRemaining(); + + /** + * Estimated time remaining on the power source, in seconds, as reported by the + * operating system. + *

+ * This is an estimated/smoothed value which should correspond to the Operating + * System's "battery time remaining" display, and will react slowly to changes + * in power consumption. * * @return If positive, seconds remaining. If negative, -1.0 (calculating) or * -2.0 (unlimited) */ - double getTimeRemaining(); + double getTimeRemainingEstimated(); + + /** + * Estimated time remaining on the power source, in seconds, as reported by the + * battery. If the battery is charging, this value may represent time remaining + * to fully charge the battery. + *

+ * Note that this value is not very accurate on some battery systems. The value + * may vary widely depending on present power usage, which could be affected by + * disk activity and other factors. This value will often be a higher value than + * {@link #getTimeRemainingEstimated()}. + * + * @return Seconds remaining to fully discharge or fully charge the battery. + */ + double getTimeRemainingInstant(); + + /** + * Power Usage Rate of the battery, in milliWatts (mW). + * + * @return If positive, the charge rate. If negative, the discharge rate. + */ + double getPowerUsageRate(); + + /** + * Voltage of the battery, in Volts. + * + * @return the battery voltage, or -1 if unknown. + */ + double getVoltage(); + + /** + * Amperage of the battery, in milliAmperes (mA). + * + * @return the battery amperage. If positive, charging the battery. If negative, + * discharging the battery. + */ + double getAmperage(); /** - * Updates remaining capacity and time remaining. + * Reports whether the device is plugged in to an external power source. + * + * @return {@code true} if plugged in, {@code false} otherwise. + */ + boolean isPowerOnLine(); + + /** + * Reports whether the battery is charging. + * + * @return {@code true} if the battery is charging, {@code false} otherwise. + */ + boolean isCharging(); + + /** + * Reports whether the battery is discharging. + * + * @return {@code true} if the battery is discharging, {@code false} otherwise. + */ + boolean isDischarging(); + + /** + * Reports =the units of {@link #getCurrentCapacity()}, + * {@link #getMaxCapacity()}, and {@link #getDesignCapacity()} + * + * @return The units of battery capacity. + */ + CapacityUnits getCapacityUnits(); + + /** + * The current (remaining) capacity of the battery. + * + * @return The current capacity. Units are defined by + * {@link #getCapacityUnits()}. + */ + int getCurrentCapacity(); + + /** + * The maximum capacity of the battery. When compared to design capacity, + * permits a measure of battery state of health. It is possible for max capacity + * to exceed design capacity. + * + * @return The maximum capacity. Units are defined by + * {@link #getCapacityUnits()}. + */ + int getMaxCapacity(); + + /** + * The design (original) capacity of the battery. When compared to maximum + * capacity, permits a measure of battery state of health. It is possible for + * max capacity to exceed design capacity. + * + * @return The design capacity. Units are defined by + * {@link #getCapacityUnits()}. + */ + int getDesignCapacity(); + + /** + * The cycle count of the battery, if known. + * + * @return The cycle count of the battery, or -1 if unknown. + */ + int getCycleCount(); + + /** + * The battery chemistry (e.g., Lithium Ion). + * + * @return the battery chemistry. + */ + String getChemistry(); + + /** + * The battery's date of manufacture. + *

+ * Some battery manufacturers encode the manufacture date in the serial number. + * Parsing this value is operating system and battery manufacturer dependent, + * and is left to the user. + * + * @return the manufacture date, if available. May be {@code null}. + */ + LocalDate getManufactureDate(); + + /** + * The name of the battery's manufacturer. + * + * @return the manufacturer name. + */ + String getManufacturer(); + + /** + * The battery's serial number. + *

+ * Some battery manufacturers encode the manufacture date in the serial number. + * Parsing this value is operating system and battery manufacturer dependent, + * and is left to the user. + * + * @return the serial number. + */ + String getSerialNumber(); + + /** + * The battery's temperature, in degrees Celsius. + * + * @return the battery's temperature, or 0 if uknown. + */ + double getTemperature(); + + /** + * Updates statistics on this battery. + * + * @return {@code true} if the update was successful. If {@code false} the + * battery statistics are unchanged. */ - void updateAttributes(); + boolean updateAttributes(); } diff --git a/oshi-core/src/main/java/oshi/hardware/common/AbstractPowerSource.java b/oshi-core/src/main/java/oshi/hardware/common/AbstractPowerSource.java index 20a75273bf1..efe53127d7b 100644 --- a/oshi-core/src/main/java/oshi/hardware/common/AbstractPowerSource.java +++ b/oshi-core/src/main/java/oshi/hardware/common/AbstractPowerSource.java @@ -23,60 +23,270 @@ */ package oshi.hardware.common; +import java.time.LocalDate; + +import com.sun.jna.Platform; // NOSONAR squid:S1191 + +import oshi.SystemInfo; import oshi.hardware.PowerSource; +import oshi.hardware.platform.linux.LinuxPowerSource; +import oshi.hardware.platform.mac.MacPowerSource; +import oshi.hardware.platform.unix.freebsd.FreeBsdPowerSource; +import oshi.hardware.platform.unix.solaris.SolarisPowerSource; +import oshi.hardware.platform.windows.WindowsPowerSource; +import oshi.util.Constants; /** * A Power Source */ public abstract class AbstractPowerSource implements PowerSource { - protected String name; - - protected double remainingCapacity; + private String name; + private String deviceName; + private double remainingCapacityPercent; + private double timeRemainingEstimated; + private double timeRemainingInstant; + private double powerUsageRate; + private double voltage; + private double amperage; + private boolean powerOnLine; + private boolean charging; + private boolean discharging; + private CapacityUnits capacityUnits; + private int currentCapacity; + private int maxCapacity; + private int designCapacity; + private int cycleCount; + private String chemistry; + private LocalDate manufactureDate; + private String manufacturer; + private String serialNumber; + private double temperature; - protected double timeRemaining; - - /** - * Super constructor used by platform-specific implementations of PowerSource - * - * @param newName - * The name to assign - * @param newRemainingCapacity - * Fraction of remaining capacity - * @param newTimeRemaining - * Seconds of time remaining - */ - public AbstractPowerSource(String newName, double newRemainingCapacity, double newTimeRemaining) { - this.name = newName; - this.remainingCapacity = newRemainingCapacity; - this.timeRemaining = newTimeRemaining; + public AbstractPowerSource(String name, String deviceName, double remainingCapacityPercent, + double timeRemainingEstimated, double timeRemainingInstant, double powerUsageRate, double voltage, + double amperage, boolean powerOnLine, boolean charging, boolean discharging, CapacityUnits capacityUnits, + int currentCapacity, int maxCapacity, int designCapacity, int cycleCount, String chemistry, + LocalDate manufactureDate, String manufacturer, String serialNumber, double temperature) { + super(); + this.name = name; + this.deviceName = deviceName; + this.remainingCapacityPercent = remainingCapacityPercent; + this.timeRemainingEstimated = timeRemainingEstimated; + this.timeRemainingInstant = timeRemainingInstant; + this.powerUsageRate = powerUsageRate; + this.voltage = voltage; + this.amperage = amperage; + this.powerOnLine = powerOnLine; + this.charging = charging; + this.discharging = discharging; + this.capacityUnits = capacityUnits; + this.currentCapacity = currentCapacity; + this.maxCapacity = maxCapacity; + this.designCapacity = designCapacity; + this.cycleCount = cycleCount; + this.chemistry = chemistry; + this.manufactureDate = manufactureDate; + this.manufacturer = manufacturer; + this.serialNumber = serialNumber; + this.temperature = temperature; } - /** {@inheritDoc} */ @Override public String getName() { return this.name; } - /** {@inheritDoc} */ + @Override + public String getDeviceName() { + return this.deviceName; + } + @Override public double getRemainingCapacity() { - return this.remainingCapacity; + return getRemainingCapacityPercent(); + } + + @Override + public double getRemainingCapacityPercent() { + return this.remainingCapacityPercent; } - /** {@inheritDoc} */ @Override public double getTimeRemaining() { - return this.timeRemaining; + return getTimeRemainingEstimated(); + } + + @Override + public double getTimeRemainingEstimated() { + return this.timeRemainingEstimated; + } + + @Override + public double getTimeRemainingInstant() { + return this.timeRemainingInstant; + } + + @Override + public double getPowerUsageRate() { + return this.powerUsageRate; + } + + @Override + public double getVoltage() { + return this.voltage; + } + + @Override + public double getAmperage() { + return this.amperage; + } + + @Override + public boolean isPowerOnLine() { + return this.powerOnLine; + } + + @Override + public boolean isCharging() { + return this.charging; + } + + @Override + public boolean isDischarging() { + return this.discharging; + } + + @Override + public CapacityUnits getCapacityUnits() { + return this.capacityUnits; + } + + @Override + public int getCurrentCapacity() { + return this.currentCapacity; + } + + @Override + public int getMaxCapacity() { + return this.maxCapacity; + } + + @Override + public int getDesignCapacity() { + return this.designCapacity; + } + + @Override + public int getCycleCount() { + return this.cycleCount; + } + + @Override + public String getChemistry() { + return this.chemistry; + } + + @Override + public LocalDate getManufactureDate() { + return this.manufactureDate; + } + + @Override + public String getManufacturer() { + return this.manufacturer; + } + + @Override + public String getSerialNumber() { + return this.serialNumber; + } + + @Override + public double getTemperature() { + return this.temperature; + } + + @Override + public boolean updateAttributes() { + PowerSource[] psArr = getPowerSources(); + for (PowerSource ps : psArr) { + if (ps.getName().equals(this.name)) { + this.name = ps.getName(); + this.deviceName = ps.getDeviceName(); + this.remainingCapacityPercent = ps.getRemainingCapacityPercent(); + this.timeRemainingEstimated = ps.getTimeRemainingEstimated(); + this.timeRemainingInstant = ps.getTimeRemainingInstant(); + this.powerUsageRate = ps.getPowerUsageRate(); + this.voltage = ps.getVoltage(); + this.amperage = ps.getAmperage(); + this.powerOnLine = ps.isPowerOnLine(); + this.charging = ps.isCharging(); + this.discharging = ps.isDischarging(); + this.capacityUnits = ps.getCapacityUnits(); + this.currentCapacity = ps.getCurrentCapacity(); + this.maxCapacity = ps.getMaxCapacity(); + this.designCapacity = ps.getDesignCapacity(); + this.cycleCount = ps.getCycleCount(); + this.chemistry = ps.getChemistry(); + this.manufactureDate = ps.getManufactureDate(); + this.manufacturer = ps.getManufacturer(); + this.serialNumber = ps.getSerialNumber(); + this.temperature = ps.getTemperature(); + return true; + } + } + // Didn't find this battery + return false; + } + + private static PowerSource[] getPowerSources() { + switch (SystemInfo.getCurrentPlatformEnum()) { + case WINDOWS: + return WindowsPowerSource.getPowerSources(); + case MACOSX: + return MacPowerSource.getPowerSources(); + case LINUX: + return LinuxPowerSource.getPowerSources(); + case SOLARIS: + return SolarisPowerSource.getPowerSources(); + case FREEBSD: + return FreeBsdPowerSource.getPowerSources(); + default: + throw new UnsupportedOperationException("Operating system not supported: " + Platform.getOSType()); + } } - /** {@inheritDoc} */ @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("Name: ").append(getName()).append(", "); - sb.append("Remaining Capacity: ").append(getRemainingCapacity() * 100d).append("%, "); - sb.append("Time Remaining: ").append(getFormattedTimeRemaining()); + sb.append("Device Name: ").append(getDeviceName()).append(",\n "); + sb.append("RemainingCapacityPercent: ").append(getRemainingCapacityPercent() * 100).append("%, "); + sb.append("Time Remaining: ").append(formatTimeRemaining(getTimeRemainingEstimated())).append(", "); + sb.append("Time Remaining Instant: ").append(formatTimeRemaining(getTimeRemainingInstant())).append(",\n "); + sb.append("Power Usage Rate: ").append(getPowerUsageRate()).append("mW, "); + sb.append("Voltage: ").append(getVoltage()).append("V, "); + sb.append("Amperage: ").append(getAmperage()).append("mA,\n "); + sb.append("Power OnLine: ").append(isPowerOnLine()).append(", "); + sb.append("Charging: ").append(isCharging()).append(", "); + sb.append("Discharging: ").append(isDischarging()).append(",\n "); + sb.append("Capacity Units: ").append(getCapacityUnits()).append(", "); + sb.append("Current Capacity: ").append(getCurrentCapacity()).append(", "); + sb.append("Max Capacity: ").append(getMaxCapacity()).append(", "); + sb.append("Design Capacity: ").append(getDesignCapacity()).append(",\n "); + sb.append("Cycle Count: ").append(getCycleCount()).append(", "); + sb.append("Chemistry: ").append(getChemistry()).append(", "); + sb.append("Manufacture Date: ").append(getManufactureDate() != null ? getManufactureDate() : Constants.UNKNOWN) + .append(", "); + sb.append("Manufacturer: ").append(getManufacturer()).append(",\n "); + sb.append("SerialNumber: ").append(getSerialNumber()).append(", "); + sb.append("Temperature: "); + if (getTemperature() > 0) { + sb.append(getTemperature()).append("°C"); + } else { + sb.append(Constants.UNKNOWN); + } return sb.toString(); } @@ -85,13 +295,12 @@ public String toString() { * * @return formatted String of time remaining */ - private String getFormattedTimeRemaining() { - double timeInSeconds = getTimeRemaining(); + private static String formatTimeRemaining(double timeInSeconds) { String formattedTimeRemaining; - if (timeInSeconds < 1.5) { + if (timeInSeconds < -1.5) { formattedTimeRemaining = "Charging"; } else if (timeInSeconds < 0) { - formattedTimeRemaining = "Calculating"; + formattedTimeRemaining = "Unknown"; } else { int hours = (int) (timeInSeconds / 3600); int minutes = (int) (timeInSeconds % 3600 / 60); diff --git a/oshi-core/src/main/java/oshi/hardware/platform/linux/LinuxPowerSource.java b/oshi-core/src/main/java/oshi/hardware/platform/linux/LinuxPowerSource.java index ab3ef626c55..b8f2dc35389 100644 --- a/oshi-core/src/main/java/oshi/hardware/platform/linux/LinuxPowerSource.java +++ b/oshi-core/src/main/java/oshi/hardware/platform/linux/LinuxPowerSource.java @@ -24,14 +24,15 @@ package oshi.hardware.platform.linux; import java.io.File; +import java.time.LocalDate; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import java.util.Map; import oshi.hardware.PowerSource; import oshi.hardware.common.AbstractPowerSource; +import oshi.util.Constants; import oshi.util.FileUtil; import oshi.util.ParseUtil; @@ -40,25 +41,18 @@ */ public class LinuxPowerSource extends AbstractPowerSource { - private static final Logger LOG = LoggerFactory.getLogger(LinuxPowerSource.class); - private static final String PS_PATH = "/sys/class/power_supply/"; - /** - *

- * Constructor for LinuxPowerSource. - *

- * - * @param newName - * a {@link java.lang.String} object. - * @param newRemainingCapacity - * a double. - * @param newTimeRemaining - * a double. - */ - public LinuxPowerSource(String newName, double newRemainingCapacity, double newTimeRemaining) { - super(newName, newRemainingCapacity, newTimeRemaining); - LOG.debug("Initialized LinuxPowerSource"); + public LinuxPowerSource(String psName, String psDeviceName, double psRemainingCapacityPercent, + double psTimeRemainingEstimated, double psTimeRemainingInstant, double psPowerUsageRate, double psVoltage, + double psAmperage, boolean psPowerOnLine, boolean psCharging, boolean psDischarging, + CapacityUnits psCapacityUnits, int psCurrentCapacity, int psMaxCapacity, int psDesignCapacity, + int psCycleCount, String psChemistry, LocalDate psManufactureDate, String psManufacturer, + String psSerialNumber, double psTemperature) { + super(psName, psDeviceName, psRemainingCapacityPercent, psTimeRemainingEstimated, psTimeRemainingInstant, + psPowerUsageRate, psVoltage, psAmperage, psPowerOnLine, psCharging, psDischarging, psCapacityUnits, + psCurrentCapacity, psMaxCapacity, psDesignCapacity, psCycleCount, psChemistry, psManufactureDate, + psManufacturer, psSerialNumber, psTemperature); } /** @@ -67,6 +61,28 @@ public LinuxPowerSource(String newName, double newRemainingCapacity, double newT * @return An array of PowerSource objects representing batteries, etc. */ public static PowerSource[] getPowerSources() { + String psName; + String psDeviceName; + double psRemainingCapacityPercent = -1d; + double psTimeRemainingEstimated = -1d; // -1 = unknown, -2 = unlimited + double psTimeRemainingInstant = -1d; + double psPowerUsageRate = 0d; + double psVoltage = -1d; + double psAmperage = 0d; + boolean psPowerOnLine = false; + boolean psCharging = false; + boolean psDischarging = false; + CapacityUnits psCapacityUnits = CapacityUnits.RELATIVE; + int psCurrentCapacity = -1; + int psMaxCapacity = -1; + int psDesignCapacity = -1; + int psCycleCount = -1; + String psChemistry; + LocalDate psManufactureDate = null; + String psManufacturer; + String psSerialNumber; + double psTemperature = 0d; + // Get list of power source names File f = new File(PS_PATH); String[] psNames = f.list(); @@ -76,96 +92,71 @@ public static PowerSource[] getPowerSources() { } List psList = new ArrayList<>(psNames.length); // For each power source, output various info - for (String psName : psNames) { + for (String name : psNames) { // Skip if name is ADP* or AC* (AC power supply) - if (psName.startsWith("ADP") || psName.startsWith("AC")) { + if (name.startsWith("ADP") || name.startsWith("AC")) { continue; } // Skip if can't read uevent file List psInfo; - psInfo = FileUtil.readFile(PS_PATH + psName + "/uevent", false); + psInfo = FileUtil.readFile(PS_PATH + name + "/uevent", false); if (psInfo.isEmpty()) { continue; } - // Initialize defaults - boolean isPresent = false; - boolean isCharging = false; - String name = "Unknown"; - int energyNow = 0; - int energyFull = 1; - int powerNow = 1; - for (String checkLine : psInfo) { - if (checkLine.startsWith("POWER_SUPPLY_PRESENT")) { - // Skip if not present - String[] psSplit = checkLine.split("="); - if (psSplit.length > 1) { - isPresent = ParseUtil.parseIntOrDefault(psSplit[1], 0) > 0; - } - if (!isPresent) { - break; - } - } else if (checkLine.startsWith("POWER_SUPPLY_NAME")) { - // Name - String[] psSplit = checkLine.split("="); - if (psSplit.length > 1) { - name = psSplit[1]; - } - } else if (checkLine.startsWith("POWER_SUPPLY_ENERGY_NOW") - || checkLine.startsWith("POWER_SUPPLY_CHARGE_NOW")) { - // Remaining Capacity = energyNow / energyFull - String[] psSplit = checkLine.split("="); - if (psSplit.length > 1) { - energyNow = ParseUtil.parseIntOrDefault(psSplit[1], 0); - } - } else if (checkLine.startsWith("POWER_SUPPLY_ENERGY_FULL") - || checkLine.startsWith("POWER_SUPPLY_CHARGE_FULL")) { - String[] psSplit = checkLine.split("="); - if (psSplit.length > 1) { - energyFull = ParseUtil.parseIntOrDefault(psSplit[1], 1); - if (energyFull < 1) { - energyFull = 1; - } - } - } else if (checkLine.startsWith("POWER_SUPPLY_STATUS")) { - // Check if charging - String[] psSplit = checkLine.split("="); - if (psSplit.length > 1 && "Charging".equals(psSplit[1])) { - isCharging = true; - } - } else if (checkLine.startsWith("POWER_SUPPLY_POWER_NOW") - || checkLine.startsWith("POWER_SUPPLY_CURRENT_NOW")) { - // Time Remaining = energyNow / powerNow (hours) - String[] psSplit = checkLine.split("="); - if (psSplit.length > 1) { - powerNow = ParseUtil.parseIntOrDefault(psSplit[1], 1); - } - if (powerNow < 1) { - isCharging = true; - } + Map psMap = new HashMap<>(); + for (String line : psInfo) { + String[] split = line.split("="); + if (split.length > 1 && !split[1].isEmpty()) { + psMap.put(split[0], split[1]); } } - if (isPresent) { - psList.add(new LinuxPowerSource(name, (double) energyNow / energyFull, - isCharging ? -2d : 3600d * energyNow / powerNow)); + psName = psMap.getOrDefault("POWER_SUPPLY_NAME", name); + String status = psMap.get("POWER_SUPPLY_STATUS"); + psCharging = ("Charging".equals(status)); + psDischarging = ("Discharging".equals(status)); + if (psMap.containsKey("POWER_SUPPLY_CAPACITY")) { + psRemainingCapacityPercent = ParseUtil.parseIntOrDefault(psMap.get("POWER_SUPPLY_CAPACITY"), -100) + / 100d; } - } - - return psList.toArray(new LinuxPowerSource[0]); - } - - /** {@inheritDoc} */ - @Override - public void updateAttributes() { - PowerSource[] psArr = getPowerSources(); - for (PowerSource ps : psArr) { - if (ps.getName().equals(this.name)) { - this.remainingCapacity = ps.getRemainingCapacity(); - this.timeRemaining = ps.getTimeRemaining(); - return; + if (psMap.containsKey("POWER_SUPPLY_ENERGY_NOW")) { + psCurrentCapacity = ParseUtil.parseIntOrDefault(psMap.get("POWER_SUPPLY_ENERGY_NOW"), -1); + } else if (psMap.containsKey("POWER_SUPPLY_CHARGE_NOW")) { + psCurrentCapacity = ParseUtil.parseIntOrDefault(psMap.get("POWER_SUPPLY_CHARGE_NOW"), -1); + } + if (psMap.containsKey("POWER_SUPPLY_ENERGY_FULL")) { + psCurrentCapacity = ParseUtil.parseIntOrDefault(psMap.get("POWER_SUPPLY_ENERGY_FULL"), 1); + } else if (psMap.containsKey("POWER_SUPPLY_CHARGE_FULL")) { + psCurrentCapacity = ParseUtil.parseIntOrDefault(psMap.get("POWER_SUPPLY_CHARGE_FULL"), 1); + } + if (psMap.containsKey("POWER_SUPPLY_ENERGY_FULL_DESIGN")) { + psMaxCapacity = ParseUtil.parseIntOrDefault(psMap.get("POWER_SUPPLY_ENERGY_FULL_DESIGN"), 1); + } else if (psMap.containsKey("POWER_SUPPLY_CHARGE_FULL_DESIGN")) { + psMaxCapacity = ParseUtil.parseIntOrDefault(psMap.get("POWER_SUPPLY_CHARGE_FULL_DESIGN"), 1); + } + if (psMap.containsKey("POWER_SUPPLY_VOLTAGE_NOW")) { + psVoltage = ParseUtil.parseIntOrDefault(psMap.get("POWER_SUPPLY_VOLTAGE_NOW"), -1); + } + if (psMap.containsKey("POWER_SUPPLY_POWER_NOW")) { + psPowerUsageRate = ParseUtil.parseIntOrDefault(psMap.get("POWER_SUPPLY_POWER_NOW"), -1); + } + if (psVoltage > 0) { + psAmperage = psPowerUsageRate / psVoltage; + } + if (psMap.containsKey("POWER_SUPPLY_CYCLE_COUNT")) { + psCycleCount = ParseUtil.parseIntOrDefault(psMap.get("POWER_SUPPLY_CYCLE_COUNT"), -1); + } + psChemistry = psMap.getOrDefault("POWER_SUPPLY_TECHNOLOGY", Constants.UNKNOWN); + psDeviceName = psMap.getOrDefault("POWER_SUPPLY_MODEL_NAME", Constants.UNKNOWN); + psManufacturer = psMap.getOrDefault("POWER_SUPPLY_MANUFACTURER", Constants.UNKNOWN); + psSerialNumber = psMap.getOrDefault("POWER_SUPPLY_SERIAL_NUMBER", Constants.UNKNOWN); + if (ParseUtil.parseIntOrDefault(psMap.get("POWER_SUPPLY_PRESENT"), 1) > 0) { + psList.add(new LinuxPowerSource(psName, psDeviceName, psRemainingCapacityPercent, + psTimeRemainingEstimated, psTimeRemainingInstant, psPowerUsageRate, psVoltage, psAmperage, + psPowerOnLine, psCharging, psDischarging, psCapacityUnits, psCurrentCapacity, psMaxCapacity, + psDesignCapacity, psCycleCount, psChemistry, psManufactureDate, psManufacturer, psSerialNumber, + psTemperature)); } } - // Didn't find this battery - this.remainingCapacity = 0d; - this.timeRemaining = -1d; + return psList.toArray(new LinuxPowerSource[0]); } } diff --git a/oshi-core/src/main/java/oshi/hardware/platform/mac/MacPowerSource.java b/oshi-core/src/main/java/oshi/hardware/platform/mac/MacPowerSource.java index 6e509dd1c08..f0a40dc47bb 100644 --- a/oshi-core/src/main/java/oshi/hardware/platform/mac/MacPowerSource.java +++ b/oshi-core/src/main/java/oshi/hardware/platform/mac/MacPowerSource.java @@ -23,12 +23,10 @@ */ package oshi.hardware.platform.mac; +import java.time.LocalDate; import java.util.ArrayList; import java.util.List; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import com.sun.jna.Pointer; // NOSONAR squid:S1191 import oshi.hardware.PowerSource; @@ -41,6 +39,8 @@ import oshi.jna.platform.mac.CoreFoundation.CFStringRef; import oshi.jna.platform.mac.CoreFoundation.CFTypeRef; import oshi.jna.platform.mac.IOKit; +import oshi.jna.platform.mac.IOKit.IORegistryEntry; +import oshi.jna.platform.mac.IOKitUtil; import oshi.util.Constants; /** @@ -48,17 +48,89 @@ */ public class MacPowerSource extends AbstractPowerSource { - private static final Logger LOG = LoggerFactory.getLogger(MacPowerSource.class); - private static final CoreFoundation CF = CoreFoundation.INSTANCE; private static final IOKit IO = IOKit.INSTANCE; - public MacPowerSource(String newName, double newRemainingCapacity, double newTimeRemaining) { - super(newName, newRemainingCapacity, newTimeRemaining); - LOG.debug("Initialized MacPowerSource"); + public MacPowerSource(String psName, String psDeviceName, double psRemainingCapacityPercent, + double psTimeRemainingEstimated, double psTimeRemainingInstant, double psPowerUsageRate, double psVoltage, + double psAmperage, boolean psPowerOnLine, boolean psCharging, boolean psDischarging, + CapacityUnits psCapacityUnits, int psCurrentCapacity, int psMaxCapacity, int psDesignCapacity, + int psCycleCount, String psChemistry, LocalDate psManufactureDate, String psManufacturer, + String psSerialNumber, double psTemperature) { + super(psName, psDeviceName, psRemainingCapacityPercent, psTimeRemainingEstimated, psTimeRemainingInstant, + psPowerUsageRate, psVoltage, psAmperage, psPowerOnLine, psCharging, psDischarging, psCapacityUnits, + psCurrentCapacity, psMaxCapacity, psDesignCapacity, psCycleCount, psChemistry, psManufactureDate, + psManufacturer, psSerialNumber, psTemperature); } + /** + * Gets Battery Information. + * + * @return An array of PowerSource objects representing batteries, etc. + */ public static PowerSource[] getPowerSources() { + String psDeviceName = Constants.UNKNOWN; + double psTimeRemainingInstant = 0d; + double psPowerUsageRate = 0d; + double psVoltage = -1d; + double psAmperage = 0d; + boolean psPowerOnLine = false; + boolean psCharging = false; + boolean psDischarging = false; + CapacityUnits psCapacityUnits = CapacityUnits.RELATIVE; + int psCurrentCapacity = 0; + int psMaxCapacity = 1; + int psDesignCapacity = 1; + int psCycleCount = -1; + String psChemistry = Constants.UNKNOWN; + LocalDate psManufactureDate = null; + String psManufacturer = Constants.UNKNOWN; + String psSerialNumber = Constants.UNKNOWN; + double psTemperature = 0d; + + // Mac PowerSource information comes from two sources: the IOKit's IOPS + // functions (which, in theory, return an array of objects but in most cases + // should return one), and the IORegistry's entry for AppleSmartBattery, which + // always returns one object. + // + // We start by fetching the registry information, which will be replicated + // across all IOPS entries if there are more than one. + + IORegistryEntry smartBattery = IOKitUtil.getMatchingService("AppleSmartBattery"); + if (smartBattery != null) { + psDeviceName = smartBattery.getStringProperty("DeviceName"); + psManufacturer = smartBattery.getStringProperty("Manufacturer"); + psSerialNumber = smartBattery.getStringProperty("BatterySerialNumber"); + + int manufactureDate = smartBattery.getIntegerProperty("ManufactureDate"); + // Bits 0...4 => day (value 1-31; 5 bits) + // Bits 5...8 => month (value 1-12; 4 bits) + // Bits 9...15 => years since 1980 (value 0-127; 7 bits) + int day = manufactureDate & 0x1f; + int month = (manufactureDate >> 5) & 0xf; + int year80 = (manufactureDate >> 9) & 0x7f; + psManufactureDate = LocalDate.of(1980 + year80, month, day); + + psDesignCapacity = smartBattery.getIntegerProperty("DesignCapacity"); + psMaxCapacity = smartBattery.getIntegerProperty("MaxCapacity"); + psCurrentCapacity = smartBattery.getIntegerProperty("CurrentCapacity"); + psCapacityUnits = CapacityUnits.MAH; + + psTimeRemainingInstant = smartBattery.getIntegerProperty("TimeRemaining") * 60d; + psCycleCount = smartBattery.getIntegerProperty("CycleCount"); + psTemperature = smartBattery.getIntegerProperty("Temperature") / 100d; + + psVoltage = smartBattery.getIntegerProperty("Voltage") / 1000d; + psAmperage = smartBattery.getIntegerProperty("Amperage"); + psPowerUsageRate = psVoltage * psAmperage; + + psPowerOnLine = smartBattery.getBooleanProperty("ExternalConnected"); + psCharging = smartBattery.getBooleanProperty("IsCharging"); + psDischarging = !psCharging; + + smartBattery.release(); + } + // Get the blob containing current power source state CFTypeRef powerSourcesInfo = IO.IOPSCopyPowerSourcesInfo(); CFArrayRef powerSourcesList = IO.IOPSCopyPowerSourcesList(powerSourcesInfo); @@ -66,7 +138,7 @@ public static PowerSource[] getPowerSources() { // Get time remaining // -1 = unknown, -2 = unlimited - double timeRemaining = IO.IOPSGetTimeRemainingEstimate(); + double psTimeRemainingEstimated = IO.IOPSGetTimeRemainingEstimate(); CFStringRef nameKey = CFStringRef.createCFString("Name"); CFStringRef isPresentKey = CFStringRef.createCFString("Is Present"); @@ -90,25 +162,30 @@ public static PowerSource[] getPowerSources() { // Get name result = dictionary.getValue(nameKey); CFStringRef cfName = new CFStringRef(result); - String name = cfName.stringValue(); - if (name == null) { - name = Constants.UNKNOWN; + String psName = cfName.stringValue(); + if (psName == null) { + psName = Constants.UNKNOWN; } // Remaining Capacity = current / max - int currentCapacity = 0; + double currentCapacity = 0d; if (dictionary.getValueIfPresent(currentCapacityKey, null)) { result = dictionary.getValue(currentCapacityKey); CFNumberRef cap = new CFNumberRef(result); currentCapacity = cap.intValue(); } - int maxCapacity = 100; + double maxCapacity = 1d; if (dictionary.getValueIfPresent(maxCapacityKey, null)) { result = dictionary.getValue(maxCapacityKey); CFNumberRef cap = new CFNumberRef(result); maxCapacity = cap.intValue(); } + double psRemainingCapacityPercent = Math.min(1d, currentCapacity / maxCapacity); // Add to list - psList.add(new MacPowerSource(name, (double) currentCapacity / maxCapacity, timeRemaining)); + psList.add(new MacPowerSource(psName, psDeviceName, psRemainingCapacityPercent, + psTimeRemainingEstimated, psTimeRemainingInstant, psPowerUsageRate, psVoltage, psAmperage, + psPowerOnLine, psCharging, psDischarging, psCapacityUnits, psCurrentCapacity, psMaxCapacity, + psDesignCapacity, psCycleCount, psChemistry, psManufactureDate, psManufacturer, + psSerialNumber, psTemperature)); } } } @@ -122,19 +199,4 @@ public static PowerSource[] getPowerSources() { return psList.toArray(new MacPowerSource[0]); } - - @Override - public void updateAttributes() { - PowerSource[] psArr = getPowerSources(); - for (PowerSource ps : psArr) { - if (ps.getName().equals(this.name)) { - this.remainingCapacity = ps.getRemainingCapacity(); - this.timeRemaining = ps.getTimeRemaining(); - return; - } - } - // Didn't find this battery - this.remainingCapacity = 0d; - this.timeRemaining = -1d; - } } diff --git a/oshi-core/src/main/java/oshi/hardware/platform/unix/freebsd/FreeBsdPowerSource.java b/oshi-core/src/main/java/oshi/hardware/platform/unix/freebsd/FreeBsdPowerSource.java index df1389e4d79..1789e5f26d1 100644 --- a/oshi-core/src/main/java/oshi/hardware/platform/unix/freebsd/FreeBsdPowerSource.java +++ b/oshi-core/src/main/java/oshi/hardware/platform/unix/freebsd/FreeBsdPowerSource.java @@ -23,11 +23,16 @@ */ package oshi.hardware.platform.unix.freebsd; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import java.time.LocalDate; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import oshi.hardware.PowerSource; import oshi.hardware.common.AbstractPowerSource; +import oshi.util.Constants; +import oshi.util.ExecutingCommand; +import oshi.util.ParseUtil; import oshi.util.platform.unix.freebsd.BsdSysctlUtil; /** @@ -35,23 +40,16 @@ */ public class FreeBsdPowerSource extends AbstractPowerSource { - private static final Logger LOG = LoggerFactory.getLogger(FreeBsdPowerSource.class); - - /** - *

- * Constructor for FreeBsdPowerSource. - *

- * - * @param newName - * a {@link java.lang.String} object. - * @param newRemainingCapacity - * a double. - * @param newTimeRemaining - * a double. - */ - public FreeBsdPowerSource(String newName, double newRemainingCapacity, double newTimeRemaining) { - super(newName, newRemainingCapacity, newTimeRemaining); - LOG.debug("Initialized FreeBsdPowerSource"); + public FreeBsdPowerSource(String psName, String psDeviceName, double psRemainingCapacityPercent, + double psTimeRemainingEstimated, double psTimeRemainingInstant, double psPowerUsageRate, double psVoltage, + double psAmperage, boolean psPowerOnLine, boolean psCharging, boolean psDischarging, + CapacityUnits psCapacityUnits, int psCurrentCapacity, int psMaxCapacity, int psDesignCapacity, + int psCycleCount, String psChemistry, LocalDate psManufactureDate, String psManufacturer, + String psSerialNumber, double psTemperature) { + super(psName, psDeviceName, psRemainingCapacityPercent, psTimeRemainingEstimated, psTimeRemainingInstant, + psPowerUsageRate, psVoltage, psAmperage, psPowerOnLine, psCharging, psDischarging, psCapacityUnits, + psCurrentCapacity, psMaxCapacity, psDesignCapacity, psCycleCount, psChemistry, psManufactureDate, + psManufacturer, psSerialNumber, psTemperature); } /** @@ -66,24 +64,96 @@ public static PowerSource[] getPowerSources() { } private static FreeBsdPowerSource getPowerSource(String name) { + String psName = name; + double psRemainingCapacityPercent = 1d; + double psTimeRemainingEstimated = -1d; // -1 = unknown, -2 = unlimited + double psPowerUsageRate = 0d; + double psVoltage = -1d; + double psAmperage = 0d; + boolean psPowerOnLine = false; + boolean psCharging = false; + boolean psDischarging = false; + CapacityUnits psCapacityUnits = CapacityUnits.RELATIVE; + int psCurrentCapacity = 0; + int psMaxCapacity = 1; + int psDesignCapacity = 1; + int psCycleCount = -1; + LocalDate psManufactureDate = null; + + double psTemperature = 0d; + // state 0=full, 1=discharging, 2=charging int state = BsdSysctlUtil.sysctl("hw.acpi.battery.state", 0); - // time is in minutes - int time = BsdSysctlUtil.sysctl("hw.acpi.battery.time", -1); + if (state == 2) { + psCharging = true; + } else { + int time = BsdSysctlUtil.sysctl("hw.acpi.battery.time", -1); + // time is in minutes + psTimeRemainingEstimated = time < 0 ? -1d : 60d * time; + if (state == 1) { + psDischarging = true; + } + } // life is in percent - int life = BsdSysctlUtil.sysctl("hw.acpi.battery.life", 100); - double timeRemaining = -2d; - if (state < 2) { - timeRemaining = time < 0 ? -1d : 60d * time; + int life = BsdSysctlUtil.sysctl("hw.acpi.battery.life", -1); + if (life > 0) { + psRemainingCapacityPercent = life / 100d; + } + List acpiconf = ExecutingCommand.runNative("acpiconf -i 0"); + Map psMap = new HashMap<>(); + for (String line : acpiconf) { + String[] split = line.split(":", 2); + if (split.length > 1) { + String value = split[1].trim(); + if (!value.isEmpty()) { + psMap.put(split[0], value); + } + } + } + + String psDeviceName = psMap.getOrDefault("Model number", Constants.UNKNOWN); + String psSerialNumber = psMap.getOrDefault("Serial number", Constants.UNKNOWN); + String psChemistry = psMap.getOrDefault("Type", Constants.UNKNOWN); + String psManufacturer = psMap.getOrDefault("OEM info", Constants.UNKNOWN); + String cap = psMap.get("Design capacity"); + if (cap != null) { + psDesignCapacity = ParseUtil.getFirstIntValue(cap); + if (cap.toLowerCase().contains("mah")) { + psCapacityUnits = CapacityUnits.MAH; + } else if (cap.toLowerCase().contains("mwh")) { + psCapacityUnits = CapacityUnits.MWH; + } + } + cap = psMap.get("Last full capacity"); + if (cap != null) { + psMaxCapacity = ParseUtil.getFirstIntValue(cap); + } else { + psMaxCapacity = psDesignCapacity; + } + double psTimeRemainingInstant = psTimeRemainingEstimated; + String time = psMap.get("Remaining time"); + if (time != null) { + String[] hhmm = time.split(":"); + if (hhmm.length == 2) { + psTimeRemainingInstant = 3600d * ParseUtil.parseIntOrDefault(hhmm[0], 0) + + 60d * ParseUtil.parseIntOrDefault(hhmm[1], 0); + } + } + String rate = psMap.get("Present rate"); + if (rate != null) { + psPowerUsageRate = ParseUtil.getFirstIntValue(rate); + } + String volts = psMap.get("Present voltage"); + if (volts != null) { + psVoltage = ParseUtil.getFirstIntValue(volts); + if (psVoltage != 0d) { + psAmperage = psPowerUsageRate / psVoltage; + } } - return new FreeBsdPowerSource(name, life / 100d, timeRemaining); - } - /** {@inheritDoc} */ - @Override - public void updateAttributes() { - PowerSource ps = getPowerSource(this.name); - this.remainingCapacity = ps.getRemainingCapacity(); - this.timeRemaining = ps.getTimeRemaining(); + return new FreeBsdPowerSource(psName, psDeviceName, psRemainingCapacityPercent, psTimeRemainingEstimated, + psTimeRemainingInstant, psPowerUsageRate, psVoltage, psAmperage, psPowerOnLine, psCharging, + psDischarging, psCapacityUnits, psCurrentCapacity, psMaxCapacity, psDesignCapacity, psCycleCount, + psChemistry, psManufactureDate, psManufacturer, psSerialNumber, psTemperature); } } diff --git a/oshi-core/src/main/java/oshi/hardware/platform/unix/solaris/SolarisPowerSource.java b/oshi-core/src/main/java/oshi/hardware/platform/unix/solaris/SolarisPowerSource.java index 8b6f8b1e5a8..7e93f62d907 100644 --- a/oshi-core/src/main/java/oshi/hardware/platform/unix/solaris/SolarisPowerSource.java +++ b/oshi-core/src/main/java/oshi/hardware/platform/unix/solaris/SolarisPowerSource.java @@ -23,13 +23,13 @@ */ package oshi.hardware.platform.unix.solaris; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import java.time.LocalDate; import com.sun.jna.platform.unix.solaris.LibKstat.Kstat; // NOSONAR import oshi.hardware.PowerSource; import oshi.hardware.common.AbstractPowerSource; +import oshi.util.Constants; import oshi.util.platform.unix.solaris.KstatUtil; import oshi.util.platform.unix.solaris.KstatUtil.KstatChain; @@ -38,11 +38,7 @@ */ public class SolarisPowerSource extends AbstractPowerSource { - private static final Logger LOG = LoggerFactory.getLogger(SolarisPowerSource.class); - - /* - * One-time lookup to see which kstat module to use - */ + // One-time lookup to see which kstat module to use private static final String[] KSTAT_BATT_MOD = { null, "battery", "acpi_drv" }; private static final int KSTAT_BATT_IDX; @@ -59,21 +55,16 @@ public class SolarisPowerSource extends AbstractPowerSource { } } - /** - *

- * Constructor for SolarisPowerSource. - *

- * - * @param newName - * a {@link java.lang.String} object. - * @param newRemainingCapacity - * a double. - * @param newTimeRemaining - * a double. - */ - public SolarisPowerSource(String newName, double newRemainingCapacity, double newTimeRemaining) { - super(newName, newRemainingCapacity, newTimeRemaining); - LOG.debug("Initialized SolarisPowerSource"); + public SolarisPowerSource(String psName, String psDeviceName, double psRemainingCapacityPercent, + double psTimeRemainingEstimated, double psTimeRemainingInstant, double psPowerUsageRate, double psVoltage, + double psAmperage, boolean psPowerOnLine, boolean psCharging, boolean psDischarging, + CapacityUnits psCapacityUnits, int psCurrentCapacity, int psMaxCapacity, int psDesignCapacity, + int psCycleCount, String psChemistry, LocalDate psManufactureDate, String psManufacturer, + String psSerialNumber, double psTemperature) { + super(psName, psDeviceName, psRemainingCapacityPercent, psTimeRemainingEstimated, psTimeRemainingInstant, + psPowerUsageRate, psVoltage, psAmperage, psPowerOnLine, psCharging, psDischarging, psCapacityUnits, + psCurrentCapacity, psMaxCapacity, psDesignCapacity, psCycleCount, psChemistry, psManufactureDate, + psManufacturer, psSerialNumber, psTemperature); } /** @@ -88,66 +79,91 @@ public static PowerSource[] getPowerSources() { } private static SolarisPowerSource getPowerSource(String name) { - // If no kstat info, return empty - if (KSTAT_BATT_IDX == 0) { - return new SolarisPowerSource(name, 0d, -1d); - } - // Get kstat for the battery information - long energyFull; - long energyNow; - double timeRemaining = -2d; - try (KstatChain kc = KstatUtil.openChain()) { - Kstat ksp = kc.lookup(KSTAT_BATT_MOD[KSTAT_BATT_IDX], 0, "battery BIF0"); - if (ksp == null) { - return new SolarisPowerSource(name, 0d, -1d); - } - - // Predicted battery capacity when fully charged. - energyFull = KstatUtil.dataLookupLong(ksp, "bif_last_cap"); - if (energyFull == 0xffffffff || energyFull <= 0) { - energyFull = KstatUtil.dataLookupLong(ksp, "bif_design_cap"); - } - if (energyFull == 0xffffffff || energyFull <= 0) { - return new SolarisPowerSource(name, 0d, -1d); - } - - // Get kstat for the battery state - ksp = kc.lookup(KSTAT_BATT_MOD[KSTAT_BATT_IDX], 0, "battery BST0"); - if (ksp == null) { - return new SolarisPowerSource(name, 0d, -1d); - } + String psName = name; + String psDeviceName = Constants.UNKNOWN; + double psRemainingCapacityPercent = 1d; + double psTimeRemainingEstimated = -1d; // -1 = unknown, -2 = unlimited + double psTimeRemainingInstant = 0d; + double psPowerUsageRate = 0d; + double psVoltage = -1d; + double psAmperage = 0d; + boolean psPowerOnLine = false; + boolean psCharging = false; + boolean psDischarging = false; + CapacityUnits psCapacityUnits = CapacityUnits.RELATIVE; + int psCurrentCapacity = 0; + int psMaxCapacity = 1; + int psDesignCapacity = 1; + int psCycleCount = -1; + String psChemistry = Constants.UNKNOWN; + LocalDate psManufactureDate = null; + String psManufacturer = Constants.UNKNOWN; + String psSerialNumber = Constants.UNKNOWN; + double psTemperature = 0d; - // estimated remaining battery capacity - energyNow = KstatUtil.dataLookupLong(ksp, "bst_rem_cap"); - if (energyNow < 0) { - return new SolarisPowerSource(name, 0d, -1d); - } - - // power or current supplied at battery terminal - long powerNow = KstatUtil.dataLookupLong(ksp, "bst_rate"); - if (powerNow == 0xFFFFFFFF) { - powerNow = 0L; - } - - // Battery State: - // bit 0 = discharging - // bit 1 = charging - // bit 2 = critical energy state - boolean isCharging = (KstatUtil.dataLookupLong(ksp, "bst_state") & 0x10) > 0; - - if (!isCharging) { - timeRemaining = powerNow > 0 ? 3600d * energyNow / powerNow : -1d; + // If no kstat info, return empty + if (KSTAT_BATT_IDX > 0) { + // Get kstat for the battery information + try (KstatChain kc = KstatUtil.openChain()) { + Kstat ksp = kc.lookup(KSTAT_BATT_MOD[KSTAT_BATT_IDX], 0, "battery BIF0"); + if (ksp != null) { + // Predicted battery capacity when fully charged. + long energyFull = KstatUtil.dataLookupLong(ksp, "bif_last_cap"); + if (energyFull == 0xffffffff || energyFull <= 0) { + energyFull = KstatUtil.dataLookupLong(ksp, "bif_design_cap"); + } + if (energyFull != 0xffffffff && energyFull > 0) { + psMaxCapacity = (int) energyFull; + } + long unit = KstatUtil.dataLookupLong(ksp, "bif_unit"); + if (unit == 0) { + psCapacityUnits = CapacityUnits.MWH; + } else if (unit == 1) { + psCapacityUnits = CapacityUnits.MAH; + } + psDeviceName = KstatUtil.dataLookupString(ksp, "bif_model"); + psSerialNumber = KstatUtil.dataLookupString(ksp, "bif_serial"); + psChemistry = KstatUtil.dataLookupString(ksp, "bif_type"); + psManufacturer = KstatUtil.dataLookupString(ksp, "bif_oem_info"); + } + + // Get kstat for the battery state + ksp = kc.lookup(KSTAT_BATT_MOD[KSTAT_BATT_IDX], 0, "battery BST0"); + if (ksp != null) { + // estimated remaining battery capacity + long energyNow = KstatUtil.dataLookupLong(ksp, "bst_rem_cap"); + if (energyNow >= 0) { + psCurrentCapacity = (int) energyNow; + } + // power or current supplied at battery terminal + long powerNow = KstatUtil.dataLookupLong(ksp, "bst_rate"); + if (powerNow == 0xFFFFFFFF) { + powerNow = 0L; + } + // Battery State: + // bit 0 = discharging + // bit 1 = charging + // bit 2 = critical energy state + boolean isCharging = (KstatUtil.dataLookupLong(ksp, "bst_state") & 0x10) > 0; + + if (!isCharging) { + psTimeRemainingEstimated = powerNow > 0 ? 3600d * energyNow / powerNow : -1d; + } + + long voltageNow = KstatUtil.dataLookupLong(ksp, "bst_voltage"); + if (voltageNow > 0) { + psVoltage = voltageNow / 1000d; + if (psVoltage != 0d) { + psAmperage = psPowerUsageRate / psVoltage; + } + } + } } } - return new SolarisPowerSource(name, (double) energyNow / energyFull, timeRemaining); - } - - /** {@inheritDoc} */ - @Override - public void updateAttributes() { - PowerSource ps = getPowerSource(this.name); - this.remainingCapacity = ps.getRemainingCapacity(); - this.timeRemaining = ps.getTimeRemaining(); + return new SolarisPowerSource(psName, psDeviceName, psRemainingCapacityPercent, psTimeRemainingEstimated, + psTimeRemainingInstant, psPowerUsageRate, psVoltage, psAmperage, psPowerOnLine, psCharging, + psDischarging, psCapacityUnits, psCurrentCapacity, psMaxCapacity, psDesignCapacity, psCycleCount, + psChemistry, psManufactureDate, psManufacturer, psSerialNumber, psTemperature); } } diff --git a/oshi-core/src/main/java/oshi/hardware/platform/windows/WindowsPowerSource.java b/oshi-core/src/main/java/oshi/hardware/platform/windows/WindowsPowerSource.java index d80ada79a70..2d3c4de21f4 100644 --- a/oshi-core/src/main/java/oshi/hardware/platform/windows/WindowsPowerSource.java +++ b/oshi-core/src/main/java/oshi/hardware/platform/windows/WindowsPowerSource.java @@ -23,40 +23,62 @@ */ package oshi.hardware.platform.windows; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import java.nio.charset.StandardCharsets; +import java.time.LocalDate; import com.sun.jna.Memory; // NOSONAR +import com.sun.jna.Platform; +import com.sun.jna.platform.win32.Guid.GUID; +import com.sun.jna.platform.win32.Kernel32; import com.sun.jna.platform.win32.PowrProf.POWER_INFORMATION_LEVEL; +import com.sun.jna.platform.win32.SetupApi; +import com.sun.jna.platform.win32.SetupApi.SP_DEVICE_INTERFACE_DATA; +import com.sun.jna.platform.win32.WinBase; +import com.sun.jna.platform.win32.WinError; +import com.sun.jna.platform.win32.WinNT; +import com.sun.jna.platform.win32.WinNT.HANDLE; +import com.sun.jna.ptr.IntByReference; +import com.sun.jna.win32.W32APITypeMapper; import oshi.hardware.PowerSource; import oshi.hardware.common.AbstractPowerSource; import oshi.jna.platform.windows.PowrProf; +import oshi.jna.platform.windows.PowrProf.BATTERY_INFORMATION; +import oshi.jna.platform.windows.PowrProf.BATTERY_MANUFACTURE_DATE; +import oshi.jna.platform.windows.PowrProf.BATTERY_QUERY_INFORMATION; import oshi.jna.platform.windows.PowrProf.SystemBatteryState; -import oshi.util.FormatUtil; +import oshi.util.Constants; /** * A Power Source */ public class WindowsPowerSource extends AbstractPowerSource { - private static final Logger LOG = LoggerFactory.getLogger(WindowsPowerSource.class); + private static final GUID GUID_DEVCLASS_BATTERY = GUID.fromString("{72631E54-78A4-11D0-BCF7-00AA00B7B32A}"); + private static final int CHAR_WIDTH = W32APITypeMapper.DEFAULT == W32APITypeMapper.UNICODE ? 2 : 1; + private static final boolean X64 = Platform.is64Bit(); - /** - *

- * Constructor for WindowsPowerSource. - *

- * - * @param newName - * a {@link java.lang.String} object. - * @param newRemainingCapacity - * a double. - * @param newTimeRemaining - * a double. - */ - public WindowsPowerSource(String newName, double newRemainingCapacity, double newTimeRemaining) { - super(newName, newRemainingCapacity, newTimeRemaining); - LOG.debug("Initialized WindowsPowerSource"); + private static final int BATTERY_SYSTEM_BATTERY = 0x80000000; + private static final int BATTERY_IS_SHORT_TERM = 0x20000000; + private static final int BATTERY_POWER_ON_LINE = 0x00000001; + private static final int BATTERY_DISCHARGING = 0x00000002; + private static final int BATTERY_CHARGING = 0x00000004; + private static final int BATTERY_CAPACITY_RELATIVE = 0x40000000; + + private static final int IOCTL_BATTERY_QUERY_TAG = 0x294040; + private static final int IOCTL_BATTERY_QUERY_STATUS = 0x29404c; + private static final int IOCTL_BATTERY_QUERY_INFORMATION = 0x294044; + + public WindowsPowerSource(String psName, String psDeviceName, double psRemainingCapacityPercent, + double psTimeRemainingEstimated, double psTimeRemainingInstant, double psPowerUsageRate, double psVoltage, + double psAmperage, boolean psPowerOnLine, boolean psCharging, boolean psDischarging, + CapacityUnits psCapacityUnits, int psCurrentCapacity, int psMaxCapacity, int psDesignCapacity, + int psCycleCount, String psChemistry, LocalDate psManufactureDate, String psManufacturer, + String psSerialNumber, double psTemperature) { + super(psName, psDeviceName, psRemainingCapacityPercent, psTimeRemainingEstimated, psTimeRemainingInstant, + psPowerUsageRate, psVoltage, psAmperage, psPowerOnLine, psCharging, psDischarging, psCapacityUnits, + psCurrentCapacity, psMaxCapacity, psDesignCapacity, psCycleCount, psChemistry, psManufactureDate, + psManufacturer, psSerialNumber, psTemperature); } /** @@ -72,30 +94,240 @@ public static PowerSource[] getPowerSources() { } private static WindowsPowerSource getPowerSource(String name) { - // Get structure + String psName = name; + String psDeviceName = Constants.UNKNOWN; + double psRemainingCapacityPercent = 1d; + double psTimeRemainingEstimated = -1d; // -1 = unknown, -2 = unlimited + double psTimeRemainingInstant = 0d; + double psPowerUsageRate = 0d; + double psVoltage = -1d; + double psAmperage = 0d; + boolean psPowerOnLine = false; + boolean psCharging = false; + boolean psDischarging = false; + CapacityUnits psCapacityUnits = CapacityUnits.RELATIVE; + int psCurrentCapacity = 0; + int psMaxCapacity = 1; + int psDesignCapacity = 1; + int psCycleCount = -1; + String psChemistry = Constants.UNKNOWN; + LocalDate psManufactureDate = null; + String psManufacturer = Constants.UNKNOWN; + String psSerialNumber = Constants.UNKNOWN; + double psTemperature = 0d; + + // windows PowerSource information comes from two sources: the PowrProf's + // CallNTPowerInformation function which returns information for a single + // object, and DeviceIoControl with each battery's (if more than one) handle + // (which, in theory, return an array of objects but in most cases should return + // one). + // + // We start by fetching the PowrProf information, which will be replicated + // across all IOCTL entries if there are more than one. + int size = new SystemBatteryState().size(); Memory mem = new Memory(size); if (0 == PowrProf.INSTANCE.CallNtPowerInformation(POWER_INFORMATION_LEVEL.SystemBatteryState, null, 0, mem, size)) { SystemBatteryState batteryState = new SystemBatteryState(mem); if (batteryState.batteryPresent > 0) { - int estimatedTime = -2; // -1 = unknown, -2 = unlimited if (batteryState.acOnLine == 0 && batteryState.charging == 0 && batteryState.discharging > 0) { - estimatedTime = batteryState.estimatedTime; + psTimeRemainingEstimated = batteryState.estimatedTime; + } else if (batteryState.charging > 0) { + psTimeRemainingEstimated = -2d; + } + psMaxCapacity = batteryState.maxCapacity; + psCurrentCapacity = batteryState.remainingCapacity; + psRemainingCapacityPercent = Math.min(1d, (double) psCurrentCapacity / psMaxCapacity); + psPowerUsageRate = batteryState.rate; + } + } + + // Enumerate batteries and ask each one for information + // Ported from: + // https://docs.microsoft.com/en-us/windows/win32/power/enumerating-battery-devices + + HANDLE hdev = SetupApi.INSTANCE.SetupDiGetClassDevs(GUID_DEVCLASS_BATTERY, null, null, + SetupApi.DIGCF_PRESENT | SetupApi.DIGCF_DEVICEINTERFACE); + if (WinBase.INVALID_HANDLE_VALUE != hdev) { + // Limit search to 100 batteries max + for (int idev = 0; idev < 100; idev++) { + SP_DEVICE_INTERFACE_DATA did = new SP_DEVICE_INTERFACE_DATA(); + did.cbSize = did.size(); + + if (SetupApi.INSTANCE.SetupDiEnumDeviceInterfaces(hdev, null, GUID_DEVCLASS_BATTERY, idev, did)) { + IntByReference requiredSize = new IntByReference(0); + SetupApi.INSTANCE.SetupDiGetDeviceInterfaceDetail(hdev, did, null, 0, requiredSize, null); + if (WinError.ERROR_INSUFFICIENT_BUFFER == Kernel32.INSTANCE.GetLastError()) { + // PSP_DEVICE_INTERFACE_DETAIL_DATA: int size + TCHAR array + Memory pdidd = new Memory(requiredSize.getValue()); + // pdidd->cbSize is defined as sizeof(*pdidd) + // On 64 bit, cbSize is 8. On 32-bit it's 5 or 6 based on char size + // This must be set properly for the method to work but is otherwise ignored + pdidd.setInt(0, Integer.BYTES + (X64 ? 4 : CHAR_WIDTH)); + // Regardless of this setting the string portion starts after one byte + if (SetupApi.INSTANCE.SetupDiGetDeviceInterfaceDetail(hdev, did, pdidd, (int) pdidd.size(), + requiredSize, null)) { + // Enumerated a battery. Ask it for information. + String devicePath = CHAR_WIDTH > 1 ? pdidd.getWideString(Integer.BYTES) + : pdidd.getString(Integer.BYTES); + HANDLE hBattery = Kernel32.INSTANCE.CreateFile(devicePath, // pdidd->DevicePath + WinNT.GENERIC_READ | WinNT.GENERIC_WRITE, + WinNT.FILE_SHARE_READ | WinNT.FILE_SHARE_WRITE, null, WinNT.OPEN_EXISTING, + WinNT.FILE_ATTRIBUTE_NORMAL, null); + if (!WinBase.INVALID_HANDLE_VALUE.equals(hBattery)) { + // Ask the battery for its tag. + BATTERY_QUERY_INFORMATION bqi = new PowrProf.BATTERY_QUERY_INFORMATION(); + IntByReference dwWait = new IntByReference(0); + IntByReference dwTag = new IntByReference(); + IntByReference dwOut = new IntByReference(); + + if (Kernel32.INSTANCE.DeviceIoControl(hBattery, IOCTL_BATTERY_QUERY_TAG, + dwWait.getPointer(), Integer.BYTES, dwTag.getPointer(), Integer.BYTES, dwOut, + null)) { + bqi.BatteryTag = dwTag.getValue(); + if (bqi.BatteryTag > 0) { + // With the tag, you can query the battery info. + bqi.InformationLevel = PowrProf.BATTERY_QUERY_INFORMATION_LEVEL.BatteryInformation + .ordinal(); + bqi.write(); + BATTERY_INFORMATION bi = new BATTERY_INFORMATION(); + if (Kernel32.INSTANCE.DeviceIoControl(hBattery, IOCTL_BATTERY_QUERY_INFORMATION, + bqi.getPointer(), bqi.size(), bi.getPointer(), bi.size(), dwOut, + null)) { + // Only non-UPS system batteries count + bi.read(); + if (0 != (bi.Capabilities & BATTERY_SYSTEM_BATTERY) + && 0 == (bi.Capabilities & BATTERY_IS_SHORT_TERM)) { + // Capabilities flags non-mWh units + if (0 == (bi.Capabilities & BATTERY_CAPACITY_RELATIVE)) { + psCapacityUnits = CapacityUnits.MWH; + } + psChemistry = new String(bi.Chemistry, StandardCharsets.US_ASCII); + psDesignCapacity = bi.DesignedCapacity; + psMaxCapacity = bi.FullChargedCapacity; + psCycleCount = bi.CycleCount; + + // Query the battery status. + PowrProf.BATTERY_WAIT_STATUS bws = new PowrProf.BATTERY_WAIT_STATUS(); + bws.BatteryTag = bqi.BatteryTag; + bws.write(); + PowrProf.BATTERY_STATUS bs = new PowrProf.BATTERY_STATUS(); + if (Kernel32.INSTANCE.DeviceIoControl(hBattery, + IOCTL_BATTERY_QUERY_STATUS, bws.getPointer(), bws.size(), + bs.getPointer(), bs.size(), dwOut, null)) { + bs.read(); + if (0 != (bs.PowerState & BATTERY_POWER_ON_LINE)) { + psPowerOnLine = true; + } + if (0 != (bs.PowerState & BATTERY_DISCHARGING)) { + psDischarging = true; + } + if (0 != (bs.PowerState & BATTERY_CHARGING)) { + psCharging = true; + } + psCurrentCapacity = bs.Capacity; + psVoltage = bs.Voltage > 0 ? bs.Voltage / 1000d : bs.Voltage; + psPowerUsageRate = bs.Rate; + if (psVoltage > 0) { + psAmperage = psPowerUsageRate / psVoltage; + } + } + } + + psDeviceName = batteryQueryString(hBattery, dwTag.getValue(), + PowrProf.BATTERY_QUERY_INFORMATION_LEVEL.BatteryDeviceName + .ordinal()); + psManufacturer = batteryQueryString(hBattery, dwTag.getValue(), + PowrProf.BATTERY_QUERY_INFORMATION_LEVEL.BatteryManufactureName + .ordinal()); + psSerialNumber = batteryQueryString(hBattery, dwTag.getValue(), + PowrProf.BATTERY_QUERY_INFORMATION_LEVEL.BatterySerialNumber + .ordinal()); + + bqi.InformationLevel = PowrProf.BATTERY_QUERY_INFORMATION_LEVEL.BatteryManufactureDate + .ordinal(); + bqi.write(); + BATTERY_MANUFACTURE_DATE bmd = new BATTERY_MANUFACTURE_DATE(); + if (Kernel32.INSTANCE.DeviceIoControl(hBattery, + IOCTL_BATTERY_QUERY_INFORMATION, bqi.getPointer(), bqi.size(), + bmd.getPointer(), bmd.size(), dwOut, null)) { + bmd.read(); + // If failed, returns -1 for each field + if (bmd.Year > 1900 && bmd.Month > 0 && bmd.Day > 0) { + psManufactureDate = LocalDate.of(bmd.Year, bmd.Month, bmd.Day); + } + } + + bqi.InformationLevel = PowrProf.BATTERY_QUERY_INFORMATION_LEVEL.BatteryTemperature + .ordinal(); + bqi.write(); + IntByReference tempK = new IntByReference(); // 1/10 degree K + if (Kernel32.INSTANCE.DeviceIoControl(hBattery, + IOCTL_BATTERY_QUERY_INFORMATION, bqi.getPointer(), bqi.size(), + tempK.getPointer(), Integer.BYTES, dwOut, null)) { + psTemperature = tempK.getValue() / 10d - 273.15; + } + + // Put last because we change the AtRate field + bqi.InformationLevel = PowrProf.BATTERY_QUERY_INFORMATION_LEVEL.BatteryEstimatedTime + .ordinal(); + if (psPowerUsageRate != 0d) { + bqi.AtRate = (int) psPowerUsageRate; + } + bqi.write(); + IntByReference tr = new IntByReference(); + if (Kernel32.INSTANCE.DeviceIoControl(hBattery, + IOCTL_BATTERY_QUERY_INFORMATION, bqi.getPointer(), bqi.size(), + tr.getPointer(), Integer.BYTES, dwOut, null)) { + psTimeRemainingInstant = tr.getValue(); + } + // Fallback + if (psTimeRemainingInstant < 0 && psPowerUsageRate != 0d) { + psTimeRemainingInstant = 3600 * (psMaxCapacity - psCurrentCapacity) + / psPowerUsageRate; + if (psTimeRemainingInstant < 0) { + psTimeRemainingInstant *= -1; + } + } + // Exit loop + break; + } + } + } + Kernel32.INSTANCE.CloseHandle(hBattery); + } + } + } + } else if (WinError.ERROR_NO_MORE_ITEMS == Kernel32.INSTANCE.GetLastError()) { + break; // Enumeration failed - perhaps we're out of items } - long maxCapacity = FormatUtil.getUnsignedInt(batteryState.maxCapacity); - long remainingCapacity = FormatUtil.getUnsignedInt(batteryState.remainingCapacity); - return new WindowsPowerSource(name, (double) remainingCapacity / maxCapacity, estimatedTime); } + SetupApi.INSTANCE.SetupDiDestroyDeviceInfoList(hdev); } - return new WindowsPowerSource("Unknown", 0d, -1d); + + return new WindowsPowerSource(psName, psDeviceName, psRemainingCapacityPercent, psTimeRemainingEstimated, + psTimeRemainingInstant, psPowerUsageRate, psVoltage, psAmperage, psPowerOnLine, psCharging, + psDischarging, psCapacityUnits, psCurrentCapacity, psMaxCapacity, psDesignCapacity, psCycleCount, + psChemistry, psManufactureDate, psManufacturer, psSerialNumber, psTemperature); } - /** {@inheritDoc} */ - @Override - public void updateAttributes() { - PowerSource ps = getPowerSource(this.name); - this.remainingCapacity = ps.getRemainingCapacity(); - this.timeRemaining = ps.getTimeRemaining(); + private static String batteryQueryString(HANDLE hBattery, int tag, int infoLevel) { + BATTERY_QUERY_INFORMATION bqi = new PowrProf.BATTERY_QUERY_INFORMATION(); + bqi.BatteryTag = tag; + bqi.InformationLevel = infoLevel; + bqi.write(); + IntByReference dwOut = new IntByReference(); + boolean ret = false; + long bufSize = 0; + Memory nameBuf; + do { + // First increment is probably enough + bufSize += 256; + nameBuf = new Memory(bufSize); + ret = Kernel32.INSTANCE.DeviceIoControl(hBattery, IOCTL_BATTERY_QUERY_INFORMATION, bqi.getPointer(), + bqi.size(), nameBuf, (int) nameBuf.size(), dwOut, null); + } while (!ret && bufSize < 4096); + return CHAR_WIDTH > 1 ? nameBuf.getWideString(0) : nameBuf.getString(0); } } diff --git a/oshi-core/src/main/java/oshi/jna/platform/windows/PowrProf.java b/oshi-core/src/main/java/oshi/jna/platform/windows/PowrProf.java index 64315d84d60..be1bce09070 100644 --- a/oshi-core/src/main/java/oshi/jna/platform/windows/PowrProf.java +++ b/oshi-core/src/main/java/oshi/jna/platform/windows/PowrProf.java @@ -86,4 +86,56 @@ public ProcessorPowerInformation() { super(); } } + + // MOVE? + @FieldOrder({ "BatteryTag", "InformationLevel", "AtRate" }) + class BATTERY_QUERY_INFORMATION extends Structure { + public int BatteryTag; + public int InformationLevel; + public int AtRate; + } + + enum BATTERY_QUERY_INFORMATION_LEVEL { + BatteryInformation, BatteryGranularityInformation, BatteryTemperature, BatteryEstimatedTime, BatteryDeviceName, + BatteryManufactureDate, BatteryManufactureName, BatteryUniqueID, BatterySerialNumber + } + + @FieldOrder({ "Capabilities", "Technology", "Reserved", "Chemistry", "DesignedCapacity", "FullChargedCapacity", + "DefaultAlert1", "DefaultAlert2", "CriticalBias", "CycleCount" }) + class BATTERY_INFORMATION extends Structure { + public int Capabilities; + public byte Technology; + public byte[] Reserved = new byte[3]; + public byte[] Chemistry = new byte[4]; + public int DesignedCapacity; + public int FullChargedCapacity; + public int DefaultAlert1; + public int DefaultAlert2; + public int CriticalBias; + public int CycleCount; + } + + @FieldOrder({ "BatteryTag", "Timeout", "PowerState", "LowCapacity", "HighCapacity" }) + class BATTERY_WAIT_STATUS extends Structure { + public int BatteryTag; + public int Timeout; + public int PowerState; + public int LowCapacity; + public int HighCapacity; + } + + @FieldOrder({ "PowerState", "Capacity", "Voltage", "Rate" }) + class BATTERY_STATUS extends Structure { + public int PowerState; + public int Capacity; + public int Voltage; + public int Rate; + } + + @FieldOrder({ "Day", "Month", "Year" }) + class BATTERY_MANUFACTURE_DATE extends Structure { + public byte Day; + public byte Month; + public short Year; + } } diff --git a/oshi-core/src/main/java/oshi/software/os/unix/solaris/SolarisOperatingSystem.java b/oshi-core/src/main/java/oshi/software/os/unix/solaris/SolarisOperatingSystem.java index ccba36383b4..348d6728e75 100644 --- a/oshi-core/src/main/java/oshi/software/os/unix/solaris/SolarisOperatingSystem.java +++ b/oshi-core/src/main/java/oshi/software/os/unix/solaris/SolarisOperatingSystem.java @@ -82,10 +82,10 @@ public FamilyVersionInfo queryFamilyVersionInfo() { @Override protected int queryBitness(int jvmBitness) { - if (jvmBitness < 64) { - return ParseUtil.parseIntOrDefault(ExecutingCommand.getFirstAnswer("isainfo -b"), 32); + if (jvmBitness == 64) { + return 64; } - return 64; + return ParseUtil.parseIntOrDefault(ExecutingCommand.getFirstAnswer("isainfo -b"), 32); } @Override diff --git a/oshi-core/src/test/java/oshi/hardware/PowerSourceTest.java b/oshi-core/src/test/java/oshi/hardware/PowerSourceTest.java index cf2fb85cdc5..6904e088cb5 100644 --- a/oshi-core/src/test/java/oshi/hardware/PowerSourceTest.java +++ b/oshi-core/src/test/java/oshi/hardware/PowerSourceTest.java @@ -41,10 +41,10 @@ public void testPowerSource() { SystemInfo si = new SystemInfo(); PowerSource[] psArr = si.getHardware().getPowerSources(); for (PowerSource ps : psArr) { - assertTrue(ps.getRemainingCapacity() >= 0); + assertTrue(ps.getRemainingCapacityPercent() >= 0); double epsilon = 1E-6; - assertTrue(ps.getTimeRemaining() > 0 || Math.abs(ps.getTimeRemaining() - -1) < epsilon - || Math.abs(ps.getTimeRemaining() - -2) < epsilon); + assertTrue(ps.getTimeRemainingEstimated() > 0 || Math.abs(ps.getTimeRemainingEstimated() - -1) < epsilon + || Math.abs(ps.getTimeRemainingEstimated() - -2) < epsilon); } } }