2 * Copyright (c) 2010-2024 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.systeminfo.internal.model;
15 import java.math.BigDecimal;
16 import java.math.RoundingMode;
17 import java.util.HashMap;
18 import java.util.List;
21 import javax.measure.quantity.ElectricPotential;
22 import javax.measure.quantity.Frequency;
23 import javax.measure.quantity.Temperature;
24 import javax.measure.quantity.Time;
26 import org.eclipse.jdt.annotation.NonNullByDefault;
27 import org.eclipse.jdt.annotation.Nullable;
28 import org.openhab.core.library.dimension.DataAmount;
29 import org.openhab.core.library.types.DecimalType;
30 import org.openhab.core.library.types.PercentType;
31 import org.openhab.core.library.types.QuantityType;
32 import org.openhab.core.library.types.StringType;
33 import org.openhab.core.library.unit.SIUnits;
34 import org.openhab.core.library.unit.Units;
35 import org.osgi.service.component.annotations.Component;
36 import org.slf4j.Logger;
37 import org.slf4j.LoggerFactory;
39 import oshi.SystemInfo;
40 import oshi.hardware.CentralProcessor;
41 import oshi.hardware.ComputerSystem;
42 import oshi.hardware.Display;
43 import oshi.hardware.GlobalMemory;
44 import oshi.hardware.HWDiskStore;
45 import oshi.hardware.HardwareAbstractionLayer;
46 import oshi.hardware.NetworkIF;
47 import oshi.hardware.PowerSource;
48 import oshi.hardware.Sensors;
49 import oshi.software.os.OSFileStore;
50 import oshi.software.os.OSProcess;
51 import oshi.software.os.OperatingSystem;
52 import oshi.util.EdidUtil;
55 * This implementation of {@link SysteminfoInterface} is using the open source library OSHI to provide system
56 * information. OSHI is a free JNA-based (native) Operating System and Hardware Information library for Java.
58 * @author Svilen Valkanov - Initial contribution
59 * @author Lyubomir Papazov - Move the initialization logic that could potentially take long time to the
60 * initializeSysteminfo method
61 * @author Christoph Weitkamp - Update to OSHI 3.13.0 - Replaced deprecated method
62 * CentralProcessor#getSystemSerialNumber()
63 * @author Wouter Born - Update to OSHI 4.0.0 and add null annotations
64 * @author Mark Herwege - Add dynamic creation of extra channels
65 * @author Mark Herwege - Use units of measure
66 * @author Mark Herwege - Processor frequency channels
68 * @see <a href="https://github.com/oshi/oshi">OSHI GitHub repository</a>
71 @Component(service = SysteminfoInterface.class)
72 public class OSHISysteminfo implements SysteminfoInterface {
74 private final Logger logger = LoggerFactory.getLogger(OSHISysteminfo.class);
76 private @NonNullByDefault({}) HardwareAbstractionLayer hal;
78 // Dynamic objects (may be queried repeatedly)
79 private @NonNullByDefault({}) GlobalMemory memory;
80 private @NonNullByDefault({}) CentralProcessor cpu;
81 private @NonNullByDefault({}) Sensors sensors;
83 // Static objects, should be recreated on each request
84 private @NonNullByDefault({}) ComputerSystem computerSystem;
85 private @NonNullByDefault({}) OperatingSystem operatingSystem;
86 private @NonNullByDefault({}) List<NetworkIF> networks;
87 private @NonNullByDefault({}) List<Display> displays;
88 private @NonNullByDefault({}) List<OSFileStore> fileStores;
89 private @NonNullByDefault({}) List<PowerSource> powerSources;
90 private @NonNullByDefault({}) List<HWDiskStore> drives;
92 // Array containing cpu tick info to calculate CPU load, according to oshi doc:
93 // 8 long values representing time spent in User, Nice, System, Idle, IOwait, IRQ, SoftIRQ, and Steal states
94 private long[] ticks = new long[8];
95 // Map containing previous process state to calculate load by process
96 private Map<Integer, OSProcess> processTicks = new HashMap<>();
98 public static final int PRECISION_AFTER_DECIMAL_SIGN = 1;
101 * Some of the methods used in this constructor execute native code and require execute permissions
104 public OSHISysteminfo() {
105 logger.debug("OSHISysteminfo service is created");
109 public void initializeSysteminfo() {
110 logger.debug("OSHISysteminfo service starts initializing");
112 SystemInfo systemInfo = new SystemInfo();
113 hal = systemInfo.getHardware();
115 // Doesn't need regular update, they may be queried repeatedly
116 memory = hal.getMemory();
117 cpu = hal.getProcessor();
118 sensors = hal.getSensors();
120 computerSystem = hal.getComputerSystem();
121 operatingSystem = systemInfo.getOperatingSystem();
122 networks = hal.getNetworkIFs();
123 displays = hal.getDisplays();
124 fileStores = operatingSystem.getFileSystem().getFileStores();
125 powerSources = hal.getPowerSources();
126 drives = hal.getDiskStores();
129 private <T> T getDevice(List<@Nullable T> devices, int index) throws DeviceNotFoundException {
130 if (devices.size() <= index) {
131 throw new DeviceNotFoundException("Device with index: " + index + " can not be found!");
133 return (T) devices.get(index);
136 private <T> T getDevice(T @Nullable [] devices, int index) throws DeviceNotFoundException {
137 if (devices == null || devices.length <= index) {
138 throw new DeviceNotFoundException("Device with index: " + index + " can not be found!");
140 return devices[index];
143 private OSProcess getProcess(int pid) throws DeviceNotFoundException {
144 OSProcess process = operatingSystem.getProcess(pid);
145 if (process == null) {
146 throw new DeviceNotFoundException("Error while getting information for process with PID " + pid);
152 public StringType getOsFamily() {
153 String osFamily = operatingSystem.getFamily();
154 return new StringType(osFamily);
158 public StringType getOsManufacturer() {
159 String osManufacturer = operatingSystem.getManufacturer();
160 return new StringType(osManufacturer);
164 public StringType getOsVersion() {
165 String osVersion = operatingSystem.getVersionInfo().toString();
166 return new StringType(osVersion);
170 public StringType getCpuName() {
171 String name = cpu.getProcessorIdentifier().getName();
172 return new StringType(name);
176 public StringType getCpuDescription() {
177 String model = cpu.getProcessorIdentifier().getModel();
178 String family = cpu.getProcessorIdentifier().getFamily();
179 String serialNumber = computerSystem.getSerialNumber();
180 String identifier = cpu.getProcessorIdentifier().getIdentifier();
181 String vendor = cpu.getProcessorIdentifier().getVendor();
182 String architecture = cpu.getProcessorIdentifier().isCpu64bit() ? "64 bit" : "32 bit";
183 String descriptionFormatString = "Model: %s %s,family: %s, vendor: %s, sn: %s, identifier: %s ";
184 String description = String.format(descriptionFormatString, model, architecture, family, vendor, serialNumber,
187 return new StringType(description);
191 public DecimalType getCpuLogicalCores() {
192 int logicalProcessorCount = cpu.getLogicalProcessorCount();
193 return new DecimalType(logicalProcessorCount);
197 public DecimalType getCpuPhysicalCores() {
198 int physicalProcessorCount = cpu.getPhysicalProcessorCount();
199 return new DecimalType(physicalProcessorCount);
203 public @Nullable QuantityType<Frequency> getCpuMaxFreq() {
204 long maxFreq = cpu.getMaxFreq();
205 return maxFreq >= 0 ? new QuantityType<>(maxFreq, Units.HERTZ) : null;
209 public @Nullable QuantityType<Frequency> getCpuFreq(int logicalProcessorIndex) {
210 long freq = cpu.getCurrentFreq()[logicalProcessorIndex];
211 return freq >= 0 ? new QuantityType<>(freq, Units.HERTZ) : null;
215 public QuantityType<DataAmount> getMemoryTotal() {
216 long totalMemory = memory.getTotal();
217 totalMemory = getSizeInMB(totalMemory);
218 return new QuantityType<>(totalMemory, Units.MEBIBYTE);
222 public QuantityType<DataAmount> getMemoryAvailable() {
223 long availableMemory = memory.getAvailable();
224 availableMemory = getSizeInMB(availableMemory);
225 return new QuantityType<>(availableMemory, Units.MEBIBYTE);
229 public QuantityType<DataAmount> getMemoryUsed() {
230 long totalMemory = memory.getTotal();
231 long availableMemory = memory.getAvailable();
232 long usedMemory = totalMemory - availableMemory;
233 usedMemory = getSizeInMB(usedMemory);
234 return new QuantityType<>(usedMemory, Units.MEBIBYTE);
238 public QuantityType<DataAmount> getStorageTotal(int index) throws DeviceNotFoundException {
239 OSFileStore fileStore = getDevice(fileStores, index);
240 fileStore.updateAttributes();
241 long totalSpace = fileStore.getTotalSpace();
242 totalSpace = getSizeInMB(totalSpace);
243 return new QuantityType<>(totalSpace, Units.MEBIBYTE);
247 public QuantityType<DataAmount> getStorageAvailable(int index) throws DeviceNotFoundException {
248 OSFileStore fileStore = getDevice(fileStores, index);
249 fileStore.updateAttributes();
250 long freeSpace = fileStore.getUsableSpace();
251 freeSpace = getSizeInMB(freeSpace);
252 return new QuantityType<>(freeSpace, Units.MEBIBYTE);
256 public QuantityType<DataAmount> getStorageUsed(int index) throws DeviceNotFoundException {
257 OSFileStore fileStore = getDevice(fileStores, index);
258 fileStore.updateAttributes();
259 long totalSpace = fileStore.getTotalSpace();
260 long freeSpace = fileStore.getUsableSpace();
261 long usedSpace = totalSpace - freeSpace;
262 usedSpace = getSizeInMB(usedSpace);
263 return new QuantityType<>(usedSpace, Units.MEBIBYTE);
267 public @Nullable PercentType getStorageAvailablePercent(int deviceIndex) throws DeviceNotFoundException {
268 OSFileStore fileStore = getDevice(fileStores, deviceIndex);
269 fileStore.updateAttributes();
270 long totalSpace = fileStore.getTotalSpace();
271 long freeSpace = fileStore.getUsableSpace();
272 if (totalSpace > 0) {
273 double freePercentDecimal = (double) freeSpace / (double) totalSpace;
274 BigDecimal freePercent = getPercentsValue(freePercentDecimal);
275 return new PercentType(freePercent);
282 public @Nullable PercentType getStorageUsedPercent(int deviceIndex) throws DeviceNotFoundException {
283 OSFileStore fileStore = getDevice(fileStores, deviceIndex);
284 fileStore.updateAttributes();
285 long totalSpace = fileStore.getTotalSpace();
286 long freeSpace = fileStore.getUsableSpace();
287 long usedSpace = totalSpace - freeSpace;
288 if (totalSpace > 0) {
289 double usedPercentDecimal = (double) usedSpace / (double) totalSpace;
290 BigDecimal usedPercent = getPercentsValue(usedPercentDecimal);
291 return new PercentType(usedPercent);
298 public StringType getStorageName(int index) throws DeviceNotFoundException {
299 OSFileStore fileStore = getDevice(fileStores, index);
300 String name = fileStore.getName();
301 return new StringType(name);
305 public StringType getStorageType(int deviceIndex) throws DeviceNotFoundException {
306 OSFileStore fileStore = getDevice(fileStores, deviceIndex);
307 String type = fileStore.getType();
308 return new StringType(type);
312 public StringType getStorageDescription(int index) throws DeviceNotFoundException {
313 OSFileStore fileStore = getDevice(fileStores, index);
314 String description = fileStore.getDescription();
315 return new StringType(description);
319 public StringType getNetworkIp(int index) throws DeviceNotFoundException {
320 NetworkIF netInterface = getDevice(networks, index);
321 netInterface.updateAttributes();
322 String[] ipAddresses = netInterface.getIPv4addr();
323 String ipv4 = getDevice(ipAddresses, 0);
324 return new StringType(ipv4);
328 public StringType getNetworkName(int index) throws DeviceNotFoundException {
329 NetworkIF netInterface = getDevice(networks, index);
330 String name = netInterface.getName();
331 return new StringType(name);
335 public StringType getNetworkDisplayName(int index) throws DeviceNotFoundException {
336 NetworkIF netInterface = getDevice(networks, index);
337 String adapterName = netInterface.getDisplayName();
338 return new StringType(adapterName);
342 public StringType getDisplayInformation(int index) throws DeviceNotFoundException {
343 Display display = getDevice(displays, index);
345 byte[] edid = display.getEdid();
346 String manufacturer = EdidUtil.getManufacturerID(edid);
347 String product = EdidUtil.getProductID(edid);
348 String serialNumber = EdidUtil.getSerialNo(edid);
349 int width = EdidUtil.getHcm(edid);
350 int height = EdidUtil.getVcm(edid);
352 String edidFormatString = "Product %s, manufacturer %s, SN: %s, Width: %d, Height: %d";
353 String edidInfo = String.format(edidFormatString, product, manufacturer, serialNumber, width, height);
354 return new StringType(edidInfo);
358 public @Nullable QuantityType<Temperature> getSensorsCpuTemperature() {
359 BigDecimal cpuTemp = new BigDecimal(sensors.getCpuTemperature());
360 cpuTemp = cpuTemp.setScale(PRECISION_AFTER_DECIMAL_SIGN, RoundingMode.HALF_UP);
361 return cpuTemp.signum() == 1 ? new QuantityType<>(cpuTemp, SIUnits.CELSIUS) : null;
365 public @Nullable QuantityType<ElectricPotential> getSensorsCpuVoltage() {
366 BigDecimal cpuVoltage = new BigDecimal(sensors.getCpuVoltage());
367 cpuVoltage = cpuVoltage.setScale(PRECISION_AFTER_DECIMAL_SIGN, RoundingMode.HALF_UP);
368 return cpuVoltage.signum() == 1 ? new QuantityType<>(cpuVoltage, Units.VOLT) : null;
372 public @Nullable DecimalType getSensorsFanSpeed(int index) throws DeviceNotFoundException {
373 int[] fanSpeeds = sensors.getFanSpeeds();
374 int speed = 0; // 0 means unable to measure speed
375 if (index < fanSpeeds.length) {
376 speed = fanSpeeds[index];
378 throw new DeviceNotFoundException();
380 return speed > 0 ? new DecimalType(speed) : null;
384 public @Nullable QuantityType<Time> getBatteryRemainingTime(int index) throws DeviceNotFoundException {
385 PowerSource powerSource = getDevice(powerSources, index);
386 powerSource.updateAttributes();
387 double remainingTimeInSeconds = powerSource.getTimeRemainingEstimated();
388 // The getTimeRemaining() method returns (-1.0) if is calculating or (-2.0) if the time is unlimited.
389 BigDecimal remainingTime = getTimeInMinutes(remainingTimeInSeconds);
390 return remainingTime.signum() == 1 ? new QuantityType<>(remainingTime, Units.MINUTE) : null;
394 public PercentType getBatteryRemainingCapacity(int index) throws DeviceNotFoundException {
395 PowerSource powerSource = getDevice(powerSources, index);
396 powerSource.updateAttributes();
397 double remainingCapacity = powerSource.getRemainingCapacityPercent();
398 BigDecimal remainingCapacityPercents = getPercentsValue(remainingCapacity);
399 return new PercentType(remainingCapacityPercents);
403 public StringType getBatteryName(int index) throws DeviceNotFoundException {
404 PowerSource powerSource = getDevice(powerSources, index);
405 String name = powerSource.getName();
406 return new StringType(name);
410 public @Nullable PercentType getMemoryAvailablePercent() {
411 long availableMemory = memory.getAvailable();
412 long totalMemory = memory.getTotal();
413 if (totalMemory > 0) {
414 double freePercentDecimal = (double) availableMemory / (double) totalMemory;
415 BigDecimal freePercent = getPercentsValue(freePercentDecimal);
416 return new PercentType(freePercent);
423 public @Nullable PercentType getMemoryUsedPercent() {
424 long availableMemory = memory.getAvailable();
425 long totalMemory = memory.getTotal();
426 long usedMemory = totalMemory - availableMemory;
427 if (totalMemory > 0) {
428 double usedPercentDecimal = (double) usedMemory / (double) totalMemory;
429 BigDecimal usedPercent = getPercentsValue(usedPercentDecimal);
430 return new PercentType(usedPercent);
437 public StringType getDriveName(int deviceIndex) throws DeviceNotFoundException {
438 HWDiskStore drive = getDevice(drives, deviceIndex);
439 String name = drive.getName();
440 return new StringType(name);
444 public StringType getDriveModel(int deviceIndex) throws DeviceNotFoundException {
445 HWDiskStore drive = getDevice(drives, deviceIndex);
446 String model = drive.getModel();
447 return new StringType(model);
451 public StringType getDriveSerialNumber(int deviceIndex) throws DeviceNotFoundException {
452 HWDiskStore drive = getDevice(drives, deviceIndex);
453 String serialNumber = drive.getSerial();
454 return new StringType(serialNumber);
458 public QuantityType<DataAmount> getSwapTotal() {
459 long swapTotal = memory.getVirtualMemory().getSwapTotal();
460 swapTotal = getSizeInMB(swapTotal);
461 return new QuantityType<>(swapTotal, Units.MEBIBYTE);
465 public QuantityType<DataAmount> getSwapAvailable() {
466 long swapTotal = memory.getVirtualMemory().getSwapTotal();
467 long swapUsed = memory.getVirtualMemory().getSwapUsed();
468 long swapAvailable = swapTotal - swapUsed;
469 swapAvailable = getSizeInMB(swapAvailable);
470 return new QuantityType<>(swapAvailable, Units.MEBIBYTE);
474 public QuantityType<DataAmount> getSwapUsed() {
475 long swapUsed = memory.getVirtualMemory().getSwapUsed();
476 swapUsed = getSizeInMB(swapUsed);
477 return new QuantityType<>(swapUsed, Units.MEBIBYTE);
481 public @Nullable PercentType getSwapAvailablePercent() {
482 long swapTotal = memory.getVirtualMemory().getSwapTotal();
483 long swapUsed = memory.getVirtualMemory().getSwapUsed();
484 long swapAvailable = swapTotal - swapUsed;
486 double swapAvailablePercentDecimal = (double) swapAvailable / (double) swapTotal;
487 BigDecimal swapAvailablePercent = getPercentsValue(swapAvailablePercentDecimal);
488 return new PercentType(swapAvailablePercent);
495 public @Nullable PercentType getSwapUsedPercent() {
496 long swapTotal = memory.getVirtualMemory().getSwapTotal();
497 long swapUsed = memory.getVirtualMemory().getSwapUsed();
499 double swapUsedPercentDecimal = (double) swapUsed / (double) swapTotal;
500 BigDecimal swapUsedPercent = getPercentsValue(swapUsedPercentDecimal);
501 return new PercentType(swapUsedPercent);
507 private long getSizeInMB(long sizeInBytes) {
508 return Math.round(sizeInBytes / (1024D * 1024));
511 private BigDecimal getPercentsValue(double decimalFraction) {
512 BigDecimal result = new BigDecimal(decimalFraction * 100);
513 result = result.setScale(PRECISION_AFTER_DECIMAL_SIGN, RoundingMode.HALF_UP);
517 private BigDecimal getTimeInMinutes(double timeInSeconds) {
518 BigDecimal timeInMinutes = new BigDecimal(timeInSeconds / 60);
519 timeInMinutes = timeInMinutes.setScale(PRECISION_AFTER_DECIMAL_SIGN, RoundingMode.UP);
520 return timeInMinutes;
524 public @Nullable PercentType getSystemCpuLoad() {
525 PercentType load = (ticks[0] > 0) ? new PercentType(getPercentsValue(cpu.getSystemCpuLoadBetweenTicks(ticks)))
527 ticks = cpu.getSystemCpuLoadTicks();
534 * This information is available only on Mac and Linux OS.
537 public @Nullable DecimalType getCpuLoad1() {
538 BigDecimal avarageCpuLoad = getAvarageCpuLoad(1);
539 return avarageCpuLoad.signum() == -1 ? null : new DecimalType(avarageCpuLoad);
545 * This information is available only on Mac and Linux OS.
548 public @Nullable DecimalType getCpuLoad5() {
549 BigDecimal avarageCpuLoad = getAvarageCpuLoad(5);
550 return avarageCpuLoad.signum() == -1 ? null : new DecimalType(avarageCpuLoad);
556 * This information is available only on Mac and Linux OS.
559 public @Nullable DecimalType getCpuLoad15() {
560 BigDecimal avarageCpuLoad = getAvarageCpuLoad(15);
561 return avarageCpuLoad.signum() == -1 ? null : new DecimalType(avarageCpuLoad);
564 private BigDecimal getAvarageCpuLoad(int timeInMinutes) {
565 // This parameter is specified in OSHI Javadoc
567 switch (timeInMinutes) {
580 double processorLoads[] = cpu.getSystemLoadAverage(index + 1);
581 BigDecimal result = new BigDecimal(processorLoads[index]);
582 result = result.setScale(PRECISION_AFTER_DECIMAL_SIGN, RoundingMode.HALF_UP);
587 public QuantityType<Time> getCpuUptime() {
588 long seconds = operatingSystem.getSystemUptime();
589 return new QuantityType<>(getTimeInMinutes(seconds), Units.MINUTE);
593 public DecimalType getCpuThreads() {
594 int threadCount = operatingSystem.getThreadCount();
595 return new DecimalType(threadCount);
599 public StringType getNetworkMac(int networkIndex) throws DeviceNotFoundException {
600 NetworkIF network = getDevice(networks, networkIndex);
601 String mac = network.getMacaddr();
602 return new StringType(mac);
606 public DecimalType getNetworkPacketsReceived(int networkIndex) throws DeviceNotFoundException {
607 NetworkIF network = getDevice(networks, networkIndex);
608 network.updateAttributes();
609 long packRecv = network.getPacketsRecv();
610 return new DecimalType(packRecv);
614 public DecimalType getNetworkPacketsSent(int networkIndex) throws DeviceNotFoundException {
615 NetworkIF network = getDevice(networks, networkIndex);
616 network.updateAttributes();
617 long packSent = network.getPacketsSent();
618 return new DecimalType(packSent);
622 public QuantityType<DataAmount> getNetworkDataSent(int networkIndex) throws DeviceNotFoundException {
623 NetworkIF network = getDevice(networks, networkIndex);
624 network.updateAttributes();
625 long bytesSent = network.getBytesSent();
626 return new QuantityType<>(getSizeInMB(bytesSent), Units.MEBIBYTE);
630 public QuantityType<DataAmount> getNetworkDataReceived(int networkIndex) throws DeviceNotFoundException {
631 NetworkIF network = getDevice(networks, networkIndex);
632 network.updateAttributes();
633 long bytesRecv = network.getBytesRecv();
634 return new QuantityType<>(getSizeInMB(bytesRecv), Units.MEBIBYTE);
638 public int getCurrentProcessID() {
639 return operatingSystem.getProcessId();
643 public @Nullable StringType getProcessName(int pid) throws DeviceNotFoundException {
645 OSProcess process = getProcess(pid);
646 String name = process.getName();
647 return new StringType(name);
654 public @Nullable DecimalType getProcessCpuUsage(int pid) throws DeviceNotFoundException {
656 OSProcess process = getProcess(pid);
657 DecimalType load = (processTicks.containsKey(pid))
658 ? new DecimalType(getPercentsValue(process.getProcessCpuLoadBetweenTicks(processTicks.get(pid))))
660 processTicks.put(pid, process);
668 public @Nullable QuantityType<DataAmount> getProcessMemoryUsage(int pid) throws DeviceNotFoundException {
670 OSProcess process = getProcess(pid);
671 long memortInBytes = process.getResidentSetSize();
672 long memoryInMB = getSizeInMB(memortInBytes);
673 return new QuantityType<>(memoryInMB, Units.MEBIBYTE);
680 public @Nullable StringType getProcessPath(int pid) throws DeviceNotFoundException {
682 OSProcess process = getProcess(pid);
683 String path = process.getPath();
684 return new StringType(path);
691 public @Nullable DecimalType getProcessThreads(int pid) throws DeviceNotFoundException {
693 OSProcess process = getProcess(pid);
694 int threadCount = process.getThreadCount();
695 return new DecimalType(threadCount);
702 public int getNetworkIFCount() {
703 return networks.size();
707 public int getDisplayCount() {
708 return displays.size();
712 public int getFileOSStoreCount() {
713 return fileStores.size();
717 public int getPowerSourceCount() {
718 return powerSources.size();
722 public int getDriveCount() {
723 return drives.size();
727 public int getFanCount() {
728 return sensors.getFanSpeeds().length;