The system information binding provides operating system and hardware information including:
- Operating system name, version and manufacturer;
-- CPU average load for last 1, 5, 15 minutes, name, description, number of physical and logical cores, running threads number, system uptime;
+- CPU average load for last 1, 5, 15 minutes, name, description, number of physical and logical cores, running threads number, system uptime, max frequency and frequency by logical core;
- Free, total and available memory;
- Free, total and available swap memory;
- Hard drive name, model and serial number;
- **group** `battery` (deviceIndex)
- **channel** `name, remainingCapacity, remainingTime`
- **group** `cpu`
- - **channel** `name, description, load, load1, load5, load15, uptime, threads`
+ - **channel** `name, description, maxfreq, freq `(deviceIndex)`, load, load1, load5, load15, uptime, threads`
- **group** `sensors`
- - **channel** `cpuTemp, cpuVoltage, fanSpeed`
+ - **channel** `cpuTemp, cpuVoltage, fanSpeed `(deviceIndex)
- **group** `network` (deviceIndex)
- **channel** `ip, mac, networkDisplayName, networkName, packetsSent, packetsReceived, dataSent, dataReceived`
- **group** `currentProcess`
- deviceIndex ::= number >= 0
- (e.g. _storage1#available_)
-The `fanSpeed` channel in the `sensors` group may have a device index attached to the Channel.
+The channels marked with "(deviceIndex)" may have a device index attached to the Channel.
- channel ::= channel_group & # channel_id & (deviceIndex)
- deviceIndex ::= number >= 0
| load5 | Load for the last 5 minutes | Number | Medium | True |
| load15 | Load for the last 15 minutes | Number | Medium | True |
| threads | Number of threads currently running or for the process | Number | Medium | True |
+| maxfreq | CPU maximum frequency | Number:Frequency | Low | True |
+| freq | Logical processor frequency | Number:Frequency | High | True |
| path | The full path of the process | String | Low | False |
| uptime | System uptime (time after start) in minutes | Number:Time | Medium | True |
| name | Name of the device or process | String | Low | False |
## Known issues and workarounds
- Temperature readings are not well supported on standard Windows systems, run [OpenHardwareMonitor.exe](https://openhardwaremonitor.org) for the binding to get more reliable readings.
+- CPU frequency readings are not available on some OS versions.
## Reporting issues
Take a look at the console for an ERROR log message.
-If you find an issue with a support for a specific hardware or software architecture please take a look at the [OSHI issues](https://github.com/oshi/oshi/issues).
+If you find an issue with support for a specific hardware or software architecture please take a look at the [OSHI issues](https://github.com/oshi/oshi/issues).
Your problem might have be already reported and solved!
-Feel free to open a new issue there with the log message and the and information about your software or hardware configuration.
+Feel free to open a new issue there with the log message and the information about your software or hardware configuration.
For a general problem with the binding report the issue directly to openHAB.
/* CPU information*/
String CPU_Name "Name" <none> { channel="systeminfo:computer:work:cpu#name" }
String CPU_Description "Description" <none> { channel="systeminfo:computer:work:cpu#description" }
+Number:Frequency CPU_MaxFreq "CPU Max Frequency" <none> { channel="systeminfo:computer:work:cpu#maxfreq" }
+Number:Frequency CPU_Freq "CPU Frequency" <none> { channel="systeminfo:computer:work:cpu#freq" }
Number:Dimensionless CPU_Load "CPU Load" <none> { channel="systeminfo:computer:work:cpu#load" }
Number CPU_Load1 "Load (1 min)" <none> { channel="systeminfo:computer:work:cpu#load1" }
Number CPU_Load5 "Load (5 min)" <none> { channel="systeminfo:computer:work:cpu#load5" }
Number:Dimensionless Storage_Used_Percent "Used (%)" <none> { channel="systeminfo:computer:work:storage#usedPercent" }
/* Memory information*/
-Number Memory_Available "Available" <none> { channel="systeminfo:computer:work:memory#available" }
+Number:DataAmount Memory_Available "Available" <none> { channel="systeminfo:computer:work:memory#available" }
Number:DataAmount Memory_Used "Used" <none> { channel="systeminfo:computer:work:memory#used" }
Number:DataAmount Memory_Total "Total" <none> { channel="systeminfo:computer:work:memory#total" }
Number:Dimensionless Memory_Available_Percent "Available (%)" <none> { channel="systeminfo:computer:work:memory#availablePercent" }
/* Current process information*/
Number:Dimensionless Current_process_load "Load" <none> { channel="systeminfo:computer:work:currentProcess#load" }
-Number:Dimensionless Current_process_used "Used" <none> { channel="systeminfo:computer:work:currentProcess#used" }
+Number:DataAmount Current_process_used "Used" <none> { channel="systeminfo:computer:work:currentProcess#used" }
String Current_process_name "Name" <none> { channel="systeminfo:computer:work:currentProcess#name" }
Number Current_process_threads "Threads" <none> { channel="systeminfo:computer:work:currentProcess#threads" }
String Current_process_path "Path" <none> { channel="systeminfo:computer:work:currentProcess#path" }
/* Process information*/
Number:Dimensionless Process_load "Load" <none> { channel="systeminfo:computer:work:process#load" }
-Number:Dimensionless Process_used "Used" <none> { channel="systeminfo:computer:work:process#used" }
+Number:DataAmount Process_used "Used" <none> { channel="systeminfo:computer:work:process#used" }
String Process_name "Name" <none> { channel="systeminfo:computer:work:process#name" }
Number Process_threads "Threads" <none> { channel="systeminfo:computer:work:process#threads" }
String Process_path "Path" <none> { channel="systeminfo:computer:work:process#path" }
Frame label="CPU Information" {
Default item=CPU_Name
Default item=CPU_Description
+ Default item=CPU_MaxFreq
+ Default item=CPU_Freq
Default item=CPU_Load1
Default item=CPU_Load5
Default item=CPU_Load15
*
* @author Svilen Valkanov - Initial contribution
* @author Mark Herwege - Add dynamic creation of extra channels
+ * @author Mark Herwege - Processor frequency channels
*/
@NonNullByDefault
public class SysteminfoBindingConstants {
*/
public static final String CHANNEL_CPU_DESCRIPTION = "cpu#description";
+ /**
+ * Maximum frequency of the CPU
+ */
+ public static final String CHANNEL_CPU_MAXFREQ = "cpu#maxfreq";
+
+ /**
+ * Frequency of the CPU
+ */
+ public static final String CHANNEL_CPU_FREQ = "cpu#freq";
+
/**
* Average recent CPU load
*/
ChannelBuilder builder = ChannelBuilder.create(channelUID).withType(channelTypeUID)
.withConfiguration(baseChannel.getConfiguration());
builder.withLabel(channelType.getLabel() + " " + index);
+ builder.withDefaultTags(channelType.getTags());
String description = channelType.getDescription();
if (description != null) {
builder.withDescription(description);
}
+ String itemType = channelType.getItemType();
+ if (itemType != null) {
+ builder.withAcceptedItemType(itemType);
+ }
return builder.build();
}
* @author Lyubomir Papzov - Separate the creation of the systeminfo object and its initialization
* @author Wouter Born - Add null annotations
* @author Mark Herwege - Add dynamic creation of extra channels
+ * @author Mark Herwege - Processor frequency channels
*/
@NonNullByDefault
public class SysteminfoHandler extends BaseThingHandler {
List<Channel> newChannels = new ArrayList<>();
newChannels.addAll(createChannels(thingUID, CHANNEL_SENSORS_FAN_SPEED, systeminfo.getFanCount()));
+ newChannels.addAll(createChannels(thingUID, CHANNEL_CPU_FREQ, systeminfo.getCpuLogicalCores().intValue()));
if (!newChannels.isEmpty()) {
logger.debug("Creating additional channels");
newChannels.addAll(0, thing.getChannels());
case CHANNEL_SENSORS_FAN_SPEED:
state = systeminfo.getSensorsFanSpeed(deviceIndex);
break;
+ case CHANNEL_CPU_MAXFREQ:
+ state = systeminfo.getCpuMaxFreq();
+ break;
+ case CHANNEL_CPU_FREQ:
+ state = systeminfo.getCpuFreq(deviceIndex);
+ break;
case CHANNEL_CPU_LOAD:
PercentType cpuLoad = cpuLoadCache.getValue();
state = (cpuLoad != null) ? new QuantityType<>(cpuLoad, Units.PERCENT) : null;
import java.util.Map;
import javax.measure.quantity.ElectricPotential;
+import javax.measure.quantity.Frequency;
import javax.measure.quantity.Temperature;
import javax.measure.quantity.Time;
* @author Wouter Born - Update to OSHI 4.0.0 and add null annotations
* @author Mark Herwege - Add dynamic creation of extra channels
* @author Mark Herwege - Use units of measure
+ * @author Mark Herwege - Processor frequency channels
*
* @see <a href="https://github.com/oshi/oshi">OSHI GitHub repository</a>
*/
return new DecimalType(physicalProcessorCount);
}
+ @Override
+ public @Nullable QuantityType<Frequency> getCpuMaxFreq() {
+ long maxFreq = cpu.getMaxFreq();
+ return maxFreq >= 0 ? new QuantityType<>(maxFreq, Units.HERTZ) : null;
+ }
+
+ @Override
+ public @Nullable QuantityType<Frequency> getCpuFreq(int logicalProcessorIndex) {
+ long freq = cpu.getCurrentFreq()[logicalProcessorIndex];
+ return freq >= 0 ? new QuantityType<>(freq, Units.HERTZ) : null;
+ }
+
@Override
public QuantityType<DataAmount> getMemoryTotal() {
long totalMemory = memory.getTotal();
package org.openhab.binding.systeminfo.internal.model;
import javax.measure.quantity.ElectricPotential;
+import javax.measure.quantity.Frequency;
import javax.measure.quantity.Temperature;
import javax.measure.quantity.Time;
* @author Wouter Born - Add null annotations
* @author Mark Herwege - Add dynamic creation of extra channels
* @author Mark Herwege - Use units of measure
+ * @author Mark Herwege - Processor frequency channels
*/
@NonNullByDefault
public interface SysteminfoInterface {
*/
DecimalType getCpuPhysicalCores();
+ /**
+ * Get the maximum CPU frequency of the processor.
+ */
+ @Nullable
+ QuantityType<Frequency> getCpuMaxFreq();
+
+ /**
+ * Get the current CPU frequency of a logical processor.
+ */
+ @Nullable
+ QuantityType<Frequency> getCpuFreq(int logicalProcessorIndex);
+
/**
* Returns the system cpu load.
*
<channels>
<channel id="name" typeId="name"/>
<channel id="description" typeId="description"/>
+ <channel id="maxfreq" typeId="maxfreq"/>
+ <channel id="freq" typeId="freq"/>
<channel id="load" typeId="load"/>
<channel id="load1" typeId="loadAverage"/>
<channel id="load5" typeId="loadAverage"/>
<config-description-ref uri="channel-type:systeminfo:mediumpriority"/>
</channel-type>
+ <channel-type id="maxfreq" advanced="true">
+ <item-type>Number:Frequency</item-type>
+ <label>Maximum Frequency</label>
+ <description>CPU maximum frequency</description>
+ <state readOnly="true" pattern="%.0f MHz"/>
+ <config-description-ref uri="channel-type:systeminfo:lowpriority"/>
+ </channel-type>
+
+ <channel-type id="freq" advanced="true">
+ <item-type>Number:Frequency</item-type>
+ <label>Current Frequency</label>
+ <description>Logical processor frequency</description>
+ <state readOnly="true" pattern="%.0f MHz"/>
+ <config-description-ref uri="channel-type:systeminfo:highpriority"/>
+ </channel-type>
+
<channel-type id="load">
<item-type>Number:Dimensionless</item-type>
<label>Load</label>
</channel-groups>
<properties>
+ <property name="thingTypeVersion">1</property>
+
<property name="CPU Logical Cores">Not available</property>
<property name="CPU Physical Cores">Not available</property>
<property name="OS Manufacturer">Not available</property>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
+<update:update-descriptions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:update="https://openhab.org/schemas/update-description/v1.0.0"
+ xsi:schemaLocation="https://openhab.org/schemas/update-description/v1.0.0 https://openhab.org/schemas/update-description-1.0.0.xsd">
+
+ <thing-type uid="systeminfo:computer">
+ <instruction-set targetVersion="1">
+ <add-channel id="maxfreq" groupIds="cpu">
+ <type>systeminfo:maxfreq</type>
+ </add-channel>
+ <add-channel id="freq" groupIds="cpu">
+ <type>systeminfo:freq</type>
+ </add-channel>
+ </instruction-set>
+ </thing-type>
+
+</update:update-descriptions>
<dependency>
<groupId>com.github.oshi</groupId>
<artifactId>oshi-core</artifactId>
- <version>6.2.2</version>
+ <version>6.4.8</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
package org.openhab.binding.systeminfo.test;
import static java.lang.Thread.sleep;
-import static java.util.stream.Collectors.toList;
import static org.hamcrest.CoreMatchers.*;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.assertFalse;
import java.net.UnknownHostException;
import java.util.Hashtable;
import java.util.List;
+import java.util.Map;
import javax.measure.quantity.ElectricPotential;
+import javax.measure.quantity.Frequency;
import javax.measure.quantity.Temperature;
import javax.measure.quantity.Time;
* @author Lyubomir Papazov - Created a mock systeminfo object. This way, access to the user's OS will not be required,
* but mock data will be used instead, avoiding potential errors from the OS queries.
* @author Wouter Born - Migrate Groovy to Java tests
+ * @author Mark Herwege - Processor frequency channels
*/
@NonNullByDefault
@ExtendWith(MockitoExtension.class)
@MockitoSettings(strictness = Strictness.LENIENT)
public class SysteminfoOSGiTest extends JavaOSGiTest {
+
private static final String DEFAULT_TEST_THING_NAME = "work";
private static final String DEFAULT_TEST_ITEM_NAME = "test";
private static final String DEFAULT_CHANNEL_TEST_PRIORITY = "High";
private @NonNullByDefault({}) ManagedThingProvider managedThingProvider;
private @NonNullByDefault({}) ManagedItemChannelLinkProvider itemChannelLinkProvider;
private @NonNullByDefault({}) UnitProvider unitProvider;
+ private @NonNullByDefault({}) VolatileStorageService volatileStorageService;
@BeforeEach
public void setUp() {
- VolatileStorageService volatileStorageService = new VolatileStorageService();
+ volatileStorageService = new VolatileStorageService();
registerService(volatileStorageService);
// Preparing the mock with OS properties, that are used in the initialize method of SysteminfoHandler
// Make this lenient because the assertInvalidThingConfigurationValuesAreHandled test does not require them
- lenient().when(mockedSystemInfo.getCpuLogicalCores()).thenReturn(new DecimalType(2));
- lenient().when(mockedSystemInfo.getCpuPhysicalCores()).thenReturn(new DecimalType(2));
+ lenient().when(mockedSystemInfo.getCpuLogicalCores()).thenReturn(new DecimalType(1));
+ lenient().when(mockedSystemInfo.getCpuPhysicalCores()).thenReturn(new DecimalType(1));
lenient().when(mockedSystemInfo.getOsFamily()).thenReturn(new StringType("Mock OS"));
lenient().when(mockedSystemInfo.getOsManufacturer()).thenReturn(new StringType("Mock OS Manufacturer"));
lenient().when(mockedSystemInfo.getOsVersion()).thenReturn(new StringType("Mock Os Version"));
systeminfoHandlerFactory.bindSystemInfo(mockedSystemInfo);
}
- waitForAssert(() -> {
- systeminfoHandlerFactory = getService(ThingHandlerFactory.class, SysteminfoHandlerFactory.class);
- assertThat(systeminfoHandlerFactory, is(notNullValue()));
- });
-
waitForAssert(() -> {
thingRegistry = getService(ThingRegistry.class);
assertThat(thingRegistry, is(notNullValue()));
public void tearDown() {
Thing thing = systeminfoThing;
if (thing != null) {
- // Remove the systeminfo thing. The handler will be also disposed automatically
+ // Remove the systeminfo thing. The handler will also be disposed automatically
Thing removedThing = thingRegistry.forceRemove(thing.getUID());
assertThat("The systeminfo thing cannot be deleted", removedThing, is(notNullValue()));
waitForAssert(() -> {
ThingHandler systemInfoHandler = thing.getHandler();
assertThat(systemInfoHandler, is(nullValue()));
});
+ managedThingProvider.remove(thing.getUID());
}
if (testItem != null) {
}
unregisterService(mockedSystemInfo);
+ unregisterService(volatileStorageService);
}
private void initializeThingWithChannelAndPID(String channelID, String acceptedItemType, int pid) {
Channel channel = ChannelBuilder.create(channelUID, acceptedItemType).withType(channelTypeUID)
.withKind(ChannelKind.STATE).withConfiguration(channelConfig).build();
- Thing thing = ThingBuilder.create(thingTypeUID, thingUID).withConfiguration(thingConfiguration)
- .withChannel(channel).build();
+ ThingBuilder thingBuilder = ThingBuilder.create(thingTypeUID, thingUID).withConfiguration(thingConfiguration)
+ .withChannel(channel);
+ // Make sure the thingTypeVersion matches the highest version in the update instructions of the binding to avoid
+ // new channels being added and the thing not initializing
+ thingBuilder = thingBuilder.withProperties(Map.of("thingTypeVersion", "1"));
+ Thing thing = thingBuilder.build();
systeminfoThing = thing;
managedThingProvider.add(thing);
assertItemState(acceptedItemType, DEFAULT_TEST_ITEM_NAME, DEFAULT_CHANNEL_TEST_PRIORITY, UnDefType.UNDEF);
}
+ @Test
+ public void assertChannelCpuMaxFreq() {
+ String channnelID = SysteminfoBindingConstants.CHANNEL_CPU_MAXFREQ;
+ String acceptedItemType = "Number:Frequency";
+
+ QuantityType<Frequency> mockedCpuMaxFreqValue = new QuantityType<>(2500, Units.HERTZ);
+ when(mockedSystemInfo.getCpuMaxFreq()).thenReturn(mockedCpuMaxFreqValue);
+
+ initializeThingWithChannel(channnelID, acceptedItemType);
+ assertItemState(acceptedItemType, DEFAULT_TEST_ITEM_NAME, DEFAULT_CHANNEL_TEST_PRIORITY, mockedCpuMaxFreqValue);
+ }
+
+ @Test
+ public void assertChannelCpuFreq() {
+ String channnelID = SysteminfoBindingConstants.CHANNEL_CPU_FREQ;
+ String acceptedItemType = "Number:Frequency";
+
+ QuantityType<Frequency> mockedCpuFreqValue = new QuantityType<>(2500, Units.HERTZ);
+ when(mockedSystemInfo.getCpuFreq(0)).thenReturn(mockedCpuFreqValue);
+
+ initializeThingWithChannel(channnelID, acceptedItemType);
+ assertItemState(acceptedItemType, DEFAULT_TEST_ITEM_NAME, DEFAULT_CHANNEL_TEST_PRIORITY, mockedCpuFreqValue);
+ }
+
@Test
public void assertChannelCpuLoadIsUpdated() {
String channnelID = SysteminfoBindingConstants.CHANNEL_CPU_LOAD;
}
waitForAssert(() -> {
- List<DiscoveryResult> results = inbox.stream().filter(InboxPredicates.forThingUID(computerUID))
- .collect(toList());
+ List<DiscoveryResult> results = inbox.stream().filter(InboxPredicates.forThingUID(computerUID)).toList();
assertFalse(results.isEmpty(), "No Thing with UID " + computerUID.getAsString() + " in inbox");
});