[systeminfo] dynamic channels (#13562)
* Dynamic channels * Status messages i8n * Format fix * Cache process load values * Restore channel configs * Fix test * Stabilize tests * Fix CpuLoad1-5-15 update * Fix test bndrun * String equals cleanup * Fix potential null pointer in test Signed-off-by: Mark Herwege <mark.herwege@telenet.be>
This commit is contained in:
parent
a4f6159f09
commit
cf2a1afd56
|
@ -36,9 +36,6 @@ The discovery service implementation tries to resolve the computer name.
|
|||
If the resolving process fails, the computer name is set to "Unknown".
|
||||
In both cases it creates a Discovery Result with thing type **computer**.
|
||||
|
||||
It will be possible to implement creation of dynamic channels (e.g. the binding will scan how many storage devices are present and create channel groups for them).
|
||||
At the moment this is not supported.
|
||||
|
||||
## Thing configuration
|
||||
|
||||
The configuration of the Thing gives the user the possibility to update channels at different intervals.
|
||||
|
@ -82,23 +79,34 @@ In the list below, you can find, how are channel group and channels id`s related
|
|||
* **channel** `cpuTemp, cpuVoltage, fanSpeed`
|
||||
* **group** `network` (deviceIndex)
|
||||
* **channel** `ip, mac, networkDisplayName, networkName, packetsSent, packetsReceived, dataSent, dataReceived`
|
||||
* **group** `currentProcess`
|
||||
* **channel** `load, used, name, threads, path`
|
||||
* **group** `process` (pid)
|
||||
* **channel** `load, used, name, threads, path`
|
||||
|
||||
The groups marked with "(deviceIndex)" may have device index attached to the Channel Group.
|
||||
|
||||
- channel ::= channel_group & (deviceIndex) & # channel_id
|
||||
- deviceIndex ::= number > 0
|
||||
- deviceIndex ::= number >= 0
|
||||
- (e.g. *storage1#available*)
|
||||
|
||||
The `fanSpeed` channel in the `sensors` group may have a device index attached to the Channel.
|
||||
|
||||
- channel ::= channel_group & # channel_id & (deviceIndex)
|
||||
- deviceIndex ::= number >= 0
|
||||
|
||||
Channels or channel groups without a trailing index will show the data for the first device (index 0) if multiple exist.
|
||||
If only one device for a group exists, no channels or channel groups with indexes will be created.
|
||||
|
||||
The group `process` is using a configuration parameter "pid" instead of "deviceIndex".
|
||||
This makes it possible to change the tracked process at runtime.
|
||||
|
||||
The group `currentProcess` has the same channels as the `process` group without the "pid" configuration parameter.
|
||||
The PID is dynamically set to the PID of the process running openHAB.
|
||||
|
||||
The binding uses this index to get information about a specific device from a list of devices (e.g on a single computer several local disks could be installed with names C:\, D:\, E:\ - the first will have deviceIndex=0, the second deviceIndex=1 etc).
|
||||
If device with this index is not existing, the binding will display an error message on the console.
|
||||
|
||||
Unfortunately this feature can't be used at the moment without manually adding these new channel groups to the thing description (located in OH-INF/thing/computer.xml).
|
||||
|
||||
The table shows more detailed information about each Channel type.
|
||||
The binding introduces the following channels:
|
||||
|
||||
|
@ -244,6 +252,13 @@ Number Sensor_CPUTemp "CPU Temperature" <temperature> { chann
|
|||
Number Sensor_CPUVoltage "CPU Voltage" <energy> { channel="systeminfo:computer:work:sensors#cpuVoltage" }
|
||||
Number Sensor_FanSpeed "Fan speed" <fan> { channel="systeminfo:computer:work:sensors#fanSpeed" }
|
||||
|
||||
/* Current process information*/
|
||||
Number Current_process_load "Load" <none> { channel="systeminfo:computer:work:currentProcess#load" }
|
||||
Number 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 Process_load "Load" <none> { channel="systeminfo:computer:work:process#load" }
|
||||
Number Process_used "Used" <none> { channel="systeminfo:computer:work:process#used" }
|
||||
|
@ -313,6 +328,13 @@ sitemap systeminfo label="Systeminfo" {
|
|||
Default item=Sensor_CPUVoltage
|
||||
Default item=Sensor_FanSpeed
|
||||
}
|
||||
Frame label="Current Process Information" {
|
||||
Default item=Current_process_load
|
||||
Default item=Current_process_used
|
||||
Default item=Current_process_name
|
||||
Default item=Current_process_threads
|
||||
Default item=Current_process_path
|
||||
}
|
||||
Frame label="Process Information" {
|
||||
Default item=Process_load
|
||||
Default item=Process_used
|
||||
|
|
|
@ -20,13 +20,15 @@ import org.openhab.core.thing.ThingTypeUID;
|
|||
* used across the whole binding.
|
||||
*
|
||||
* @author Svilen Valkanov - Initial contribution
|
||||
* @author Mark Herwege - Add dynamic creation of extra channels
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SysteminfoBindingConstants {
|
||||
|
||||
public static final String BINDING_ID = "systeminfo";
|
||||
|
||||
public static final ThingTypeUID THING_TYPE_COMPUTER = new ThingTypeUID(BINDING_ID, "computer");
|
||||
public static final String THING_TYPE_COMPUTER_ID = "computer";
|
||||
public static final ThingTypeUID THING_TYPE_COMPUTER = new ThingTypeUID(BINDING_ID, THING_TYPE_COMPUTER_ID);
|
||||
|
||||
// Thing properties
|
||||
/**
|
||||
|
@ -56,6 +58,16 @@ public class SysteminfoBindingConstants {
|
|||
|
||||
// List of all Channel IDs
|
||||
|
||||
/**
|
||||
* Name of the channel group type for memory information
|
||||
*/
|
||||
public static final String CHANNEL_GROUP_TYPE_MEMORY = "memoryGroup";
|
||||
|
||||
/**
|
||||
* Name of the channel group for memory information
|
||||
*/
|
||||
public static final String CHANNEL_GROUP_MEMORY = "memory";
|
||||
|
||||
/**
|
||||
* Size of the available memory
|
||||
*/
|
||||
|
@ -91,6 +103,16 @@ public class SysteminfoBindingConstants {
|
|||
*/
|
||||
public static final String CHANNEL_MEMORY_HEAP_AVAILABLE = "memory#availableHeap";
|
||||
|
||||
/**
|
||||
* Name of the channel group type for swap information
|
||||
*/
|
||||
public static final String CHANNEL_GROUP_TYPE_SWAP = "swapGroup";
|
||||
|
||||
/**
|
||||
* Name of the channel group for swap information
|
||||
*/
|
||||
public static final String CHANNEL_GROUP_SWAP = "swap";
|
||||
|
||||
/**
|
||||
* Total size of swap memory
|
||||
*/
|
||||
|
@ -116,6 +138,16 @@ public class SysteminfoBindingConstants {
|
|||
*/
|
||||
public static final String CHANNEL_SWAP_USED_PERCENT = "swap#usedPercent";
|
||||
|
||||
/**
|
||||
* Name of the channel group type for drive information
|
||||
*/
|
||||
public static final String CHANNEL_GROUP_TYPE_DRIVE = "driveGroup";
|
||||
|
||||
/**
|
||||
* Name of the channel group for drive information
|
||||
*/
|
||||
public static final String CHANNEL_GROUP_DRIVE = "drive";
|
||||
|
||||
/**
|
||||
* Physical storage drive name
|
||||
*/
|
||||
|
@ -131,6 +163,16 @@ public class SysteminfoBindingConstants {
|
|||
*/
|
||||
public static final String CHANNEL_DRIVE_SERIAL = "drive#serial";
|
||||
|
||||
/**
|
||||
* Name of the channel group type for storage information
|
||||
*/
|
||||
public static final String CHANNEL_GROUP_TYPE_STORAGE = "storageGroup";
|
||||
|
||||
/**
|
||||
* Name of the channel group for storage information
|
||||
*/
|
||||
public static final String CHANNEL_GROUP_STORAGE = "storage";
|
||||
|
||||
/**
|
||||
* Name of the logical volume storage
|
||||
*/
|
||||
|
@ -171,6 +213,16 @@ public class SysteminfoBindingConstants {
|
|||
*/
|
||||
public static final String CHANNEL_STORAGE_USED_PERCENT = "storage#usedPercent";
|
||||
|
||||
/**
|
||||
* Name of the channel group type for sensors information
|
||||
*/
|
||||
public static final String CHANNEL_GROUP_TYPE_SENSORS = "sensorsGroup";
|
||||
|
||||
/**
|
||||
* Name of the channel group for sensors information
|
||||
*/
|
||||
public static final String CHANNEL_GROUP_SENSORS = "sensors";
|
||||
|
||||
/**
|
||||
* Temperature of the CPU measured from the sensors.
|
||||
*/
|
||||
|
@ -186,6 +238,16 @@ public class SysteminfoBindingConstants {
|
|||
*/
|
||||
public static final String CHANNEL_SENSORS_FAN_SPEED = "sensors#fanSpeed";
|
||||
|
||||
/**
|
||||
* Name of the channel group type for battery information
|
||||
*/
|
||||
public static final String CHANNEL_GROUP_TYPE_BATTERY = "batteryGroup";
|
||||
|
||||
/**
|
||||
* Name of the channel group for battery information
|
||||
*/
|
||||
public static final String CHANNEL_GROUP_BATTERY = "battery";
|
||||
|
||||
/**
|
||||
* Name of the battery
|
||||
*/
|
||||
|
@ -201,6 +263,16 @@ public class SysteminfoBindingConstants {
|
|||
*/
|
||||
public static final String CHANNEL_BATTERY_REMAINING_TIME = "battery#remainingTime";
|
||||
|
||||
/**
|
||||
* Name of the channel group type for CPU information
|
||||
*/
|
||||
public static final String CHANNEL_GROUP_TYPE_CPU = "cpuGroup";
|
||||
|
||||
/**
|
||||
* Name of the channel group for CPU information
|
||||
*/
|
||||
public static final String CHANNEL_GROUP_CPU = "cpu";
|
||||
|
||||
/**
|
||||
* Detailed description about the CPU
|
||||
*/
|
||||
|
@ -241,11 +313,31 @@ public class SysteminfoBindingConstants {
|
|||
*/
|
||||
public static final String CHANNEL_CPU_THREADS = "cpu#threads";
|
||||
|
||||
/**
|
||||
* Name of the channel group type for display information
|
||||
*/
|
||||
public static final String CHANNEL_GROUP_TYPE_DISPLAY = "displayGroup";
|
||||
|
||||
/**
|
||||
* Name of the channel group for display information
|
||||
*/
|
||||
public static final String CHANNEL_GROUP_DISPLAY = "display";
|
||||
|
||||
/**
|
||||
* Information about the display device
|
||||
*/
|
||||
public static final String CHANNEL_DISPLAY_INFORMATION = "display#information";
|
||||
|
||||
/**
|
||||
* Name of the channel group type for network information
|
||||
*/
|
||||
public static final String CHANNEL_GROUP_TYPE_NETWORK = "networkGroup";
|
||||
|
||||
/**
|
||||
* Name of the channel group for network information
|
||||
*/
|
||||
public static final String CHANNEL_GROUP_NETWORK = "network";
|
||||
|
||||
/**
|
||||
* Host IP address of the network
|
||||
*/
|
||||
|
@ -286,6 +378,47 @@ public class SysteminfoBindingConstants {
|
|||
*/
|
||||
public static final String CHANNEL_NETWORK_MAC = "network#mac";
|
||||
|
||||
/**
|
||||
* Name of the channel group type for process information
|
||||
*/
|
||||
public static final String CHANNEL_GROUP_TYPE_CURRENT_PROCESS = "currentProcessGroup";
|
||||
|
||||
/**
|
||||
* Name of the channel group for process information
|
||||
*/
|
||||
public static final String CHANNEL_GROUP_CURRENT_PROCESS = "currentProcess";
|
||||
|
||||
/**
|
||||
* CPU load used from a process
|
||||
*/
|
||||
|
||||
public static final String CHANNEL_CURRENT_PROCESS_LOAD = "currentProcess#load";
|
||||
|
||||
/**
|
||||
* Size of memory used from a process in MB
|
||||
*/
|
||||
public static final String CHANNEL_CURRENT_PROCESS_MEMORY = "currentProcess#used";
|
||||
|
||||
/**
|
||||
* Name of the process
|
||||
*/
|
||||
public static final String CHANNEL_CURRENT_PROCESS_NAME = "currentProcess#name";
|
||||
|
||||
/**
|
||||
* Number of threads, used form the process
|
||||
*/
|
||||
public static final String CHANNEL_CURRENT_PROCESS_THREADS = "currentProcess#threads";
|
||||
|
||||
/**
|
||||
* The full path of the process
|
||||
*/
|
||||
public static final String CHANNEL_CURRENT_PROCESS_PATH = "currentProcess#path";
|
||||
|
||||
/**
|
||||
* Name of the channel group type for process information
|
||||
*/
|
||||
public static final String CHANNEL_GROUP_TYPE_PROCESS = "processGroup";
|
||||
|
||||
/**
|
||||
* Name of the channel group for process information
|
||||
*/
|
||||
|
|
|
@ -12,10 +12,7 @@
|
|||
*/
|
||||
package org.openhab.binding.systeminfo.internal;
|
||||
|
||||
import static org.openhab.binding.systeminfo.internal.SysteminfoBindingConstants.THING_TYPE_COMPUTER;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
import static org.openhab.binding.systeminfo.internal.SysteminfoBindingConstants.*;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
@ -36,28 +33,33 @@ import org.osgi.service.component.annotations.Reference;
|
|||
* @author Svilen Valkanov - Initial contribution
|
||||
* @author Lyubomir Papazov - Pass systeminfo service to the SysteminfoHandler constructor
|
||||
* @author Wouter Born - Add null annotations
|
||||
* @author Mark Herwege - Add dynamic creation of extra channels
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(service = ThingHandlerFactory.class, configurationPid = "binding.systeminfo")
|
||||
public class SysteminfoHandlerFactory extends BaseThingHandlerFactory {
|
||||
|
||||
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections.singleton(THING_TYPE_COMPUTER);
|
||||
|
||||
private @NonNullByDefault({}) SysteminfoInterface systeminfo;
|
||||
private @NonNullByDefault({}) SysteminfoThingTypeProvider thingTypeProvider;
|
||||
|
||||
@Override
|
||||
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
|
||||
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
|
||||
return BINDING_ID.equals(thingTypeUID.getBindingId())
|
||||
&& thingTypeUID.getId().startsWith(THING_TYPE_COMPUTER_ID);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable ThingHandler createHandler(Thing thing) {
|
||||
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
|
||||
|
||||
if (thingTypeUID.equals(THING_TYPE_COMPUTER)) {
|
||||
return new SysteminfoHandler(thing, systeminfo);
|
||||
if (supportsThingType(thingTypeUID)) {
|
||||
String extString = "-" + thing.getUID().getId();
|
||||
ThingTypeUID extThingTypeUID = new ThingTypeUID(BINDING_ID, THING_TYPE_COMPUTER_ID + extString);
|
||||
if (thingTypeProvider.getThingType(extThingTypeUID, null) == null) {
|
||||
thingTypeProvider.createThingType(extThingTypeUID);
|
||||
thingTypeProvider.storeChannelsConfig(thing); // Save the current channels configs, will be restored
|
||||
// after thing type change.
|
||||
}
|
||||
return new SysteminfoHandler(thing, thingTypeProvider, systeminfo);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -69,4 +71,13 @@ public class SysteminfoHandlerFactory extends BaseThingHandlerFactory {
|
|||
public void unbindSystemInfo(SysteminfoInterface systeminfo) {
|
||||
this.systeminfo = null;
|
||||
}
|
||||
|
||||
@Reference
|
||||
public void setSysteminfoThingTypeProvider(SysteminfoThingTypeProvider thingTypeProvider) {
|
||||
this.thingTypeProvider = thingTypeProvider;
|
||||
}
|
||||
|
||||
public void unsetSysteminfoThingTypeProvider(SysteminfoThingTypeProvider thingTypeProvider) {
|
||||
this.thingTypeProvider = null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,279 @@
|
|||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.systeminfo.internal;
|
||||
|
||||
import static org.openhab.binding.systeminfo.internal.SysteminfoBindingConstants.*;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.config.core.Configuration;
|
||||
import org.openhab.core.thing.Channel;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.openhab.core.thing.binding.BaseThingHandler;
|
||||
import org.openhab.core.thing.binding.ThingTypeProvider;
|
||||
import org.openhab.core.thing.binding.builder.ChannelBuilder;
|
||||
import org.openhab.core.thing.type.ChannelGroupDefinition;
|
||||
import org.openhab.core.thing.type.ChannelGroupType;
|
||||
import org.openhab.core.thing.type.ChannelGroupTypeRegistry;
|
||||
import org.openhab.core.thing.type.ChannelGroupTypeUID;
|
||||
import org.openhab.core.thing.type.ChannelType;
|
||||
import org.openhab.core.thing.type.ChannelTypeRegistry;
|
||||
import org.openhab.core.thing.type.ChannelTypeUID;
|
||||
import org.openhab.core.thing.type.ThingType;
|
||||
import org.openhab.core.thing.type.ThingTypeBuilder;
|
||||
import org.openhab.core.thing.type.ThingTypeRegistry;
|
||||
import org.osgi.service.component.annotations.Activate;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Reference;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Extended channels can be auto discovered and added to newly created groups in the {@link SystemInfoHandler}. The
|
||||
* thing needs to be updated to add the groups. The `SysteminfoThingTypeProvider` OSGi service gives access to the
|
||||
* `ThingTypeRegistry` and serves the updated `ThingType`.
|
||||
*
|
||||
* @author Mark Herwege - Initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(service = { SysteminfoThingTypeProvider.class, ThingTypeProvider.class })
|
||||
public class SysteminfoThingTypeProvider implements ThingTypeProvider {
|
||||
private final Logger logger = LoggerFactory.getLogger(SysteminfoThingTypeProvider.class);
|
||||
|
||||
private final ThingTypeRegistry thingTypeRegistry;
|
||||
private final ChannelGroupTypeRegistry channelGroupTypeRegistry;
|
||||
private final ChannelTypeRegistry channelTypeRegistry;
|
||||
|
||||
private final Map<ThingTypeUID, ThingType> thingTypes = new HashMap<>();
|
||||
|
||||
private final Map<ThingUID, Map<String, Configuration>> thingChannelsConfig = new HashMap<>();
|
||||
|
||||
@Activate
|
||||
public SysteminfoThingTypeProvider(@Reference ThingTypeRegistry thingTypeRegistry,
|
||||
@Reference ChannelGroupTypeRegistry channelGroupTypeRegistry,
|
||||
@Reference ChannelTypeRegistry channelTypeRegistry) {
|
||||
super();
|
||||
this.thingTypeRegistry = thingTypeRegistry;
|
||||
this.channelGroupTypeRegistry = channelGroupTypeRegistry;
|
||||
this.channelTypeRegistry = channelTypeRegistry;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<ThingType> getThingTypes(@Nullable Locale locale) {
|
||||
return thingTypes.values();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable ThingType getThingType(ThingTypeUID thingTypeUID, @Nullable Locale locale) {
|
||||
return thingTypes.get(thingTypeUID);
|
||||
}
|
||||
|
||||
private void setThingType(ThingTypeUID uid, ThingType type) {
|
||||
thingTypes.put(uid, type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create thing type with the provided typeUID and add it to the thing type registry.
|
||||
*
|
||||
* @param typeUID
|
||||
* @return false if base type UID `systeminfo:computer` cannot be found in the thingTypeRegistry
|
||||
*/
|
||||
public boolean createThingType(ThingTypeUID typeUID) {
|
||||
logger.trace("Creating thing type {}", typeUID);
|
||||
return updateThingType(typeUID, getChannelGroupDefinitions(typeUID));
|
||||
}
|
||||
|
||||
/**
|
||||
* Update `ThingType`with `typeUID`, replacing the channel group definitions with `groupDefs`.
|
||||
*
|
||||
* @param typeUID
|
||||
* @param groupDefs
|
||||
* @return false if `typeUID` or its base type UID `systeminfo:computer` cannot be found in the thingTypeRegistry
|
||||
*/
|
||||
public boolean updateThingType(ThingTypeUID typeUID, List<ChannelGroupDefinition> groupDefs) {
|
||||
ThingTypeUID baseTypeUID = THING_TYPE_COMPUTER;
|
||||
if (thingTypes.containsKey(typeUID)) {
|
||||
baseTypeUID = typeUID;
|
||||
}
|
||||
ThingType baseType = thingTypeRegistry.getThingType(baseTypeUID);
|
||||
ThingTypeBuilder builder = createThingTypeBuilder(typeUID, baseTypeUID);
|
||||
if (baseType != null && builder != null) {
|
||||
logger.trace("Adding channel group definitions to thing type");
|
||||
ThingType type = builder.withChannelGroupDefinitions(groupDefs).build();
|
||||
|
||||
setThingType(typeUID, type);
|
||||
return true;
|
||||
} else {
|
||||
logger.debug("Error adding channel groups");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a {@link ThingTypeBuilder} that can create an exact copy of the `ThingType` with `baseTypeUID`.
|
||||
* Further build steps can be performed on the returned object before recreating the `ThingType` from the builder.
|
||||
*
|
||||
* @param newTypeUID
|
||||
* @param baseTypeUID
|
||||
* @return the ThingTypeBuilder, null if `baseTypeUID` cannot be found in the thingTypeRegistry
|
||||
*/
|
||||
private @Nullable ThingTypeBuilder createThingTypeBuilder(ThingTypeUID newTypeUID, ThingTypeUID baseTypeUID) {
|
||||
ThingType type = thingTypeRegistry.getThingType(baseTypeUID);
|
||||
|
||||
if (type == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
ThingTypeBuilder result = ThingTypeBuilder.instance(newTypeUID, type.getLabel())
|
||||
.withChannelGroupDefinitions(type.getChannelGroupDefinitions())
|
||||
.withChannelDefinitions(type.getChannelDefinitions())
|
||||
.withExtensibleChannelTypeIds(type.getExtensibleChannelTypeIds())
|
||||
.withSupportedBridgeTypeUIDs(type.getSupportedBridgeTypeUIDs()).withProperties(type.getProperties())
|
||||
.isListed(false);
|
||||
|
||||
String representationProperty = type.getRepresentationProperty();
|
||||
if (representationProperty != null) {
|
||||
result = result.withRepresentationProperty(representationProperty);
|
||||
}
|
||||
URI configDescriptionURI = type.getConfigDescriptionURI();
|
||||
if (configDescriptionURI != null) {
|
||||
result = result.withConfigDescriptionURI(configDescriptionURI);
|
||||
}
|
||||
String category = type.getCategory();
|
||||
if (category != null) {
|
||||
result = result.withCategory(category);
|
||||
}
|
||||
String description = type.getDescription();
|
||||
if (description != null) {
|
||||
result = result.withDescription(description);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return List of {@link ChannelGroupDefinition} for `ThingType` with `typeUID`. If the `ThingType` does not exist
|
||||
* in the thingTypeRegistry yet, retrieve list of `ChannelGroupDefinition` for base type systeminfo:computer.
|
||||
*
|
||||
* @param typeUID UID for ThingType
|
||||
* @return list of channel group definitions, empty list if no channel group definitions
|
||||
*/
|
||||
public List<ChannelGroupDefinition> getChannelGroupDefinitions(ThingTypeUID typeUID) {
|
||||
ThingType type = thingTypeRegistry.getThingType(typeUID);
|
||||
if (type == null) {
|
||||
type = thingTypeRegistry.getThingType(THING_TYPE_COMPUTER);
|
||||
}
|
||||
if (type != null) {
|
||||
return type.getChannelGroupDefinitions();
|
||||
} else {
|
||||
logger.debug("Cannot retrieve channel group definitions, no base thing type found");
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new channel group definition with index appended to id and label.
|
||||
*
|
||||
* @param channelGroupID id of channel group without index
|
||||
* @param channelGroupTypeID id ChannelGroupType for new channel group definition
|
||||
* @param i index
|
||||
* @return channel group definition, null if provided channelGroupTypeID cannot be found in ChannelGroupTypeRegistry
|
||||
*/
|
||||
public @Nullable ChannelGroupDefinition createChannelGroupDefinitionWithIndex(String channelGroupID,
|
||||
String channelGroupTypeID, int i) {
|
||||
ChannelGroupTypeUID channelGroupTypeUID = new ChannelGroupTypeUID(BINDING_ID, channelGroupTypeID);
|
||||
ChannelGroupType channelGroupType = channelGroupTypeRegistry.getChannelGroupType(channelGroupTypeUID);
|
||||
if (channelGroupType == null) {
|
||||
logger.debug("Cannot create channel group definition, group type {} invalid", channelGroupTypeID);
|
||||
return null;
|
||||
}
|
||||
String index = String.valueOf(i);
|
||||
return new ChannelGroupDefinition(channelGroupID + index, channelGroupTypeUID,
|
||||
channelGroupType.getLabel() + " " + index, channelGroupType.getDescription());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new channel with index appended to id and label of an existing channel.
|
||||
*
|
||||
* @param thing containing the existing channel
|
||||
* @param channelID id of channel without index
|
||||
* @param i index
|
||||
* @return channel, null if provided channelID does not match a channel, or no type can be retrieved for the
|
||||
* provided channel
|
||||
*/
|
||||
public @Nullable Channel createChannelWithIndex(Thing thing, String channelID, int i) {
|
||||
Channel baseChannel = thing.getChannel(channelID);
|
||||
if (baseChannel == null) {
|
||||
logger.debug("Cannot create channel, ID {} invalid", channelID);
|
||||
return null;
|
||||
}
|
||||
ChannelTypeUID channelTypeUID = baseChannel.getChannelTypeUID();
|
||||
ChannelType channelType = channelTypeRegistry.getChannelType(channelTypeUID);
|
||||
if (channelType == null) {
|
||||
logger.debug("Cannot create channel, type {} invalid",
|
||||
channelTypeUID != null ? channelTypeUID.getId() : "null");
|
||||
return null;
|
||||
}
|
||||
ThingUID thingUID = thing.getUID();
|
||||
String index = String.valueOf(i);
|
||||
ChannelUID channelUID = new ChannelUID(thingUID, channelID + index);
|
||||
ChannelBuilder builder = ChannelBuilder.create(channelUID).withType(channelTypeUID)
|
||||
.withConfiguration(baseChannel.getConfiguration());
|
||||
builder.withLabel(channelType.getLabel() + " " + index);
|
||||
String description = channelType.getDescription();
|
||||
if (description != null) {
|
||||
builder.withDescription(description);
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Store the channel configurations for a thing, to be able to restore them later when the thing handler for the
|
||||
* same thing gets recreated with a new thing type. This is necessary because the
|
||||
* {@link BaseThingHandler#changeThingType()} method reverts channel configurations to their defaults.
|
||||
*
|
||||
* @param thing
|
||||
*/
|
||||
public void storeChannelsConfig(Thing thing) {
|
||||
Map<String, Configuration> channelsConfig = thing.getChannels().stream()
|
||||
.collect(Collectors.toMap(c -> c.getUID().getId(), c -> c.getConfiguration()));
|
||||
thingChannelsConfig.put(thing.getUID(), channelsConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore previous channel configurations of matching channels when the thing handler gets recreated with a new
|
||||
* thing type. Return an empty map if no channel configurations where stored. Before returning previous channel
|
||||
* configurations, clear the store, so they can only be retrieved ones, immediately after a thing type change. See
|
||||
* also {@link #storeChannelsConfig(Thing)}.
|
||||
*
|
||||
* @param UID
|
||||
* @return Map of ChannelId and Configuration for the channel
|
||||
*/
|
||||
public Map<String, Configuration> restoreChannelsConfig(ThingUID UID) {
|
||||
Map<String, Configuration> configs = thingChannelsConfig.remove(UID);
|
||||
return configs != null ? configs : Collections.emptyMap();
|
||||
}
|
||||
}
|
|
@ -15,6 +15,8 @@ package org.openhab.binding.systeminfo.internal.handler;
|
|||
import static org.openhab.binding.systeminfo.internal.SysteminfoBindingConstants.*;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
@ -22,12 +24,17 @@ import java.util.Map;
|
|||
import java.util.Set;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.systeminfo.internal.SysteminfoThingTypeProvider;
|
||||
import org.openhab.binding.systeminfo.internal.model.DeviceNotFoundException;
|
||||
import org.openhab.binding.systeminfo.internal.model.SysteminfoInterface;
|
||||
import org.openhab.core.cache.ExpiringCache;
|
||||
import org.openhab.core.cache.ExpiringCacheMap;
|
||||
import org.openhab.core.config.core.Configuration;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.PercentType;
|
||||
import org.openhab.core.library.types.QuantityType;
|
||||
import org.openhab.core.library.unit.Units;
|
||||
|
@ -36,7 +43,11 @@ import org.openhab.core.thing.ChannelUID;
|
|||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.openhab.core.thing.binding.BaseThingHandler;
|
||||
import org.openhab.core.thing.binding.builder.ThingBuilder;
|
||||
import org.openhab.core.thing.type.ChannelGroupDefinition;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
import org.openhab.core.types.State;
|
||||
|
@ -51,6 +62,7 @@ import org.slf4j.LoggerFactory;
|
|||
* @author Svilen Valkanov - Initial contribution
|
||||
* @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
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SysteminfoHandler extends BaseThingHandler {
|
||||
|
@ -91,31 +103,59 @@ public class SysteminfoHandler extends BaseThingHandler {
|
|||
*/
|
||||
public static final int WAIT_TIME_CHANNEL_ITEM_LINK_INIT = 1;
|
||||
|
||||
/**
|
||||
* String used to extend thingUID and channelGroupTypeUID for thing definition with added dynamic channels and
|
||||
* extended channels. It is set in the constructor and unique to the thing.
|
||||
*/
|
||||
public final String idExtString;
|
||||
|
||||
public final SysteminfoThingTypeProvider thingTypeProvider;
|
||||
|
||||
private SysteminfoInterface systeminfo;
|
||||
|
||||
private @Nullable ScheduledFuture<?> highPriorityTasks;
|
||||
private @Nullable ScheduledFuture<?> mediumPriorityTasks;
|
||||
|
||||
private Logger logger = LoggerFactory.getLogger(SysteminfoHandler.class);
|
||||
/**
|
||||
* Caches for cpu process load and process load for a given pid. Using this cache limits the process load refresh
|
||||
* interval to the minimum interval. Too frequent refreshes leads to inaccurate results. This could happen when the
|
||||
* same process is tracked as current process and as a channel with pid parameter, or when the task interval is set
|
||||
* too low.
|
||||
*/
|
||||
private static final int MIN_PROCESS_LOAD_REFRESH_INTERVAL_MS = 2000;
|
||||
private ExpiringCache<PercentType> cpuLoadCache = new ExpiringCache<>(MIN_PROCESS_LOAD_REFRESH_INTERVAL_MS,
|
||||
() -> getSystemCpuLoad());
|
||||
private ExpiringCacheMap<Integer, @Nullable DecimalType> processLoadCache = new ExpiringCacheMap<>(
|
||||
MIN_PROCESS_LOAD_REFRESH_INTERVAL_MS);
|
||||
|
||||
public SysteminfoHandler(Thing thing, @Nullable SysteminfoInterface systeminfo) {
|
||||
private final Logger logger = LoggerFactory.getLogger(SysteminfoHandler.class);
|
||||
|
||||
public SysteminfoHandler(Thing thing, SysteminfoThingTypeProvider thingTypeProvider,
|
||||
SysteminfoInterface systeminfo) {
|
||||
super(thing);
|
||||
if (systeminfo != null) {
|
||||
this.thingTypeProvider = thingTypeProvider;
|
||||
this.systeminfo = systeminfo;
|
||||
} else {
|
||||
throw new IllegalArgumentException("No systeminfo service was provided");
|
||||
}
|
||||
|
||||
idExtString = "-" + thing.getUID().getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
logger.trace("Initializing thing {} with thing type {}", thing.getUID().getId(),
|
||||
thing.getThingTypeUID().getId());
|
||||
restoreChannelsConfig(); // After a thing type change, previous channel configs will have been stored, and will
|
||||
// be restored here.
|
||||
if (instantiateSysteminfoLibrary() && isConfigurationValid() && updateProperties()) {
|
||||
if (!addDynamicChannels()) { // If there are new channel groups, the thing will get recreated with a new
|
||||
// thing type and this handler will be disposed. Therefore do not do anything
|
||||
// further here.
|
||||
groupChannelsByPriority();
|
||||
scheduleUpdates();
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
}
|
||||
} else {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.HANDLER_INITIALIZING_ERROR,
|
||||
"Thing cannot be initialized!");
|
||||
"@text/offline.cannot-initialize");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -169,15 +209,129 @@ public class SysteminfoHandler extends BaseThingHandler {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve info on available storages, drives, displays, batteries, network interfaces and fans in the system. If
|
||||
* there is more than 1, create additional channel groups and channels representing each of the entities with an
|
||||
* index added to the channel groups and channels. The base channel groups and channels will remain without index
|
||||
* and are equal to the channel groups and channels with index 0. If there is only one entity in a group, do not add
|
||||
* a channels group and channels with index 0.
|
||||
* <p>
|
||||
* If channel groups are added, the thing type will change to systeminfo:computer-Ext, with Ext equal to the thing
|
||||
* id. A new handler will be created and initialization restarted. Therefore further initialization of the current
|
||||
* handler can be aborted if the method returns true.
|
||||
*
|
||||
* @return true if channel groups where added
|
||||
*/
|
||||
private boolean addDynamicChannels() {
|
||||
ThingUID thingUID = thing.getUID();
|
||||
|
||||
List<ChannelGroupDefinition> newChannelGroups = new ArrayList<>();
|
||||
newChannelGroups.addAll(createChannelGroups(thingUID, CHANNEL_GROUP_STORAGE, CHANNEL_GROUP_TYPE_STORAGE,
|
||||
systeminfo.getFileOSStoreCount()));
|
||||
newChannelGroups.addAll(createChannelGroups(thingUID, CHANNEL_GROUP_DRIVE, CHANNEL_GROUP_TYPE_DRIVE,
|
||||
systeminfo.getDriveCount()));
|
||||
newChannelGroups.addAll(createChannelGroups(thingUID, CHANNEL_GROUP_DISPLAY, CHANNEL_GROUP_TYPE_DISPLAY,
|
||||
systeminfo.getDisplayCount()));
|
||||
newChannelGroups.addAll(createChannelGroups(thingUID, CHANNEL_GROUP_BATTERY, CHANNEL_GROUP_TYPE_BATTERY,
|
||||
systeminfo.getPowerSourceCount()));
|
||||
newChannelGroups.addAll(createChannelGroups(thingUID, CHANNEL_GROUP_NETWORK, CHANNEL_GROUP_TYPE_NETWORK,
|
||||
systeminfo.getNetworkIFCount()));
|
||||
if (!newChannelGroups.isEmpty()) {
|
||||
logger.debug("Creating additional channel groups");
|
||||
newChannelGroups.addAll(0, thingTypeProvider.getChannelGroupDefinitions(thing.getThingTypeUID()));
|
||||
ThingTypeUID thingTypeUID = new ThingTypeUID(BINDING_ID, THING_TYPE_COMPUTER_ID + idExtString);
|
||||
if (thingTypeProvider.updateThingType(thingTypeUID, newChannelGroups)) {
|
||||
logger.trace("Channel groups were added, changing the thing type");
|
||||
changeThingType(thingTypeUID, thing.getConfiguration());
|
||||
} else {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.HANDLER_INITIALIZING_ERROR,
|
||||
"@text/offline.cannot-initialize");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
List<Channel> newChannels = new ArrayList<>();
|
||||
newChannels.addAll(createChannels(thingUID, CHANNEL_SENSORS_FAN_SPEED, systeminfo.getFanCount()));
|
||||
if (!newChannels.isEmpty()) {
|
||||
logger.debug("Creating additional channels");
|
||||
newChannels.addAll(0, thing.getChannels());
|
||||
ThingBuilder thingBuilder = editThing();
|
||||
thingBuilder.withChannels(newChannels);
|
||||
updateThing(thingBuilder.build());
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private List<ChannelGroupDefinition> createChannelGroups(ThingUID thingUID, String channelGroupID,
|
||||
String channelGroupTypeID, int count) {
|
||||
if (count <= 1) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
List<String> channelGroups = thingTypeProvider.getChannelGroupDefinitions(thing.getThingTypeUID()).stream()
|
||||
.map(ChannelGroupDefinition::getId).collect(Collectors.toList());
|
||||
|
||||
List<ChannelGroupDefinition> newChannelGroups = new ArrayList<>();
|
||||
for (int i = 0; i < count; i++) {
|
||||
String index = String.valueOf(i);
|
||||
ChannelGroupDefinition channelGroupDef = thingTypeProvider
|
||||
.createChannelGroupDefinitionWithIndex(channelGroupID, channelGroupTypeID, i);
|
||||
if (!(channelGroupDef == null || channelGroups.contains(channelGroupID + index))) {
|
||||
logger.trace("Adding channel group {}", channelGroupID + index);
|
||||
newChannelGroups.add(channelGroupDef);
|
||||
}
|
||||
}
|
||||
return newChannelGroups;
|
||||
}
|
||||
|
||||
private List<Channel> createChannels(ThingUID thingUID, String channelID, int count) {
|
||||
if (count <= 1) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
List<Channel> newChannels = new ArrayList<>();
|
||||
for (int i = 0; i < count; i++) {
|
||||
Channel channel = thingTypeProvider.createChannelWithIndex(thing, channelID, i);
|
||||
if (channel != null && thing.getChannel(channel.getUID()) == null) {
|
||||
logger.trace("Creating channel {}", channel.getUID().getId());
|
||||
newChannels.add(channel);
|
||||
}
|
||||
}
|
||||
return newChannels;
|
||||
}
|
||||
|
||||
private void storeChannelsConfig() {
|
||||
logger.trace("Storing channel configurations");
|
||||
thingTypeProvider.storeChannelsConfig(thing);
|
||||
}
|
||||
|
||||
private void restoreChannelsConfig() {
|
||||
logger.trace("Restoring channel configurations");
|
||||
Map<String, Configuration> channelsConfig = thingTypeProvider.restoreChannelsConfig(thing.getUID());
|
||||
for (String channelId : channelsConfig.keySet()) {
|
||||
Channel channel = thing.getChannel(channelId);
|
||||
Configuration config = channelsConfig.get(channelId);
|
||||
if (channel != null && config != null) {
|
||||
Configuration currentConfig = channel.getConfiguration();
|
||||
for (String param : config.keySet()) {
|
||||
if (isConfigurationKeyChanged(currentConfig, config, param)) {
|
||||
handleChannelConfigurationChange(channel, config, param);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void groupChannelsByPriority() {
|
||||
logger.trace("Grouping channels by priority.");
|
||||
logger.trace("Grouping channels by priority");
|
||||
List<Channel> channels = this.thing.getChannels();
|
||||
|
||||
for (Channel channel : channels) {
|
||||
Configuration properties = channel.getConfiguration();
|
||||
String priority = (String) properties.get(PRIOIRITY_PARAM);
|
||||
if (priority == null) {
|
||||
logger.debug("Channel with UID {} will not be updated. The channel has no priority set !",
|
||||
logger.debug("Channel with UID {} will not be updated. The channel has no priority set!",
|
||||
channel.getUID());
|
||||
break;
|
||||
}
|
||||
|
@ -220,23 +374,27 @@ public class SysteminfoHandler extends BaseThingHandler {
|
|||
}
|
||||
|
||||
private void scheduleUpdates() {
|
||||
logger.debug("Schedule high priority tasks at fixed rate {} s.", refreshIntervalHighPriority);
|
||||
logger.debug("Schedule high priority tasks at fixed rate {} s", refreshIntervalHighPriority);
|
||||
highPriorityTasks = scheduler.scheduleWithFixedDelay(() -> {
|
||||
publishData(highPriorityChannels);
|
||||
}, WAIT_TIME_CHANNEL_ITEM_LINK_INIT, refreshIntervalHighPriority.intValue(), TimeUnit.SECONDS);
|
||||
|
||||
logger.debug("Schedule medium priority tasks at fixed rate {} s.", refreshIntervalMediumPriority);
|
||||
logger.debug("Schedule medium priority tasks at fixed rate {} s", refreshIntervalMediumPriority);
|
||||
mediumPriorityTasks = scheduler.scheduleWithFixedDelay(() -> {
|
||||
publishData(mediumPriorityChannels);
|
||||
}, WAIT_TIME_CHANNEL_ITEM_LINK_INIT, refreshIntervalMediumPriority.intValue(), TimeUnit.SECONDS);
|
||||
|
||||
logger.debug("Schedule one time update for low priority tasks.");
|
||||
logger.debug("Schedule one time update for low priority tasks");
|
||||
scheduler.schedule(() -> {
|
||||
publishData(lowPriorityChannels);
|
||||
}, WAIT_TIME_CHANNEL_ITEM_LINK_INIT, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
private void publishData(Set<ChannelUID> channels) {
|
||||
// if handler disposed while waiting for the links, don't update the channel states
|
||||
if (!ThingStatus.ONLINE.equals(thing.getStatus())) {
|
||||
return;
|
||||
}
|
||||
Iterator<ChannelUID> iter = channels.iterator();
|
||||
while (iter.hasNext()) {
|
||||
ChannelUID channeUID = iter.next();
|
||||
|
@ -276,16 +434,16 @@ public class SysteminfoHandler extends BaseThingHandler {
|
|||
State state = null;
|
||||
|
||||
String channelID = channelUID.getId();
|
||||
String channelIDWithoutGroup = channelUID.getIdWithoutGroup();
|
||||
String channelGroupID = channelUID.getGroupId();
|
||||
|
||||
int deviceIndex = getDeviceIndex(channelUID);
|
||||
|
||||
// The channelGroup may contain deviceIndex. It must be deleted from the channelID, because otherwise the
|
||||
// switch will not find the correct method below.
|
||||
// All digits are deleted from the ID
|
||||
if (channelGroupID != null) {
|
||||
channelID = channelGroupID.replaceAll("\\d+", "") + "#" + channelIDWithoutGroup;
|
||||
logger.trace("Getting state for channel {} with device index {}", channelID, deviceIndex);
|
||||
|
||||
// The channelGroup or channel may contain deviceIndex. It must be deleted from the channelID, because otherwise
|
||||
// the switch will not find the correct method below.
|
||||
// All digits are deleted from the ID, except for CpuLoad channels.
|
||||
if (!(CHANNEL_CPU_LOAD_1.equals(channelID) || CHANNEL_CPU_LOAD_5.equals(channelID)
|
||||
|| CHANNEL_CPU_LOAD_15.equals(channelID))) {
|
||||
channelID = channelID.replaceAll("\\d+", "");
|
||||
}
|
||||
|
||||
try {
|
||||
|
@ -319,7 +477,7 @@ public class SysteminfoHandler extends BaseThingHandler {
|
|||
state = systeminfo.getSensorsFanSpeed(deviceIndex);
|
||||
break;
|
||||
case CHANNEL_CPU_LOAD:
|
||||
PercentType cpuLoad = systeminfo.getSystemCpuLoad();
|
||||
PercentType cpuLoad = cpuLoadCache.getValue();
|
||||
state = (cpuLoad != null) ? new QuantityType<>(cpuLoad, Units.PERCENT) : null;
|
||||
break;
|
||||
case CHANNEL_CPU_LOAD_1:
|
||||
|
@ -431,34 +589,52 @@ public class SysteminfoHandler extends BaseThingHandler {
|
|||
state = systeminfo.getNetworkPacketsSent(deviceIndex);
|
||||
break;
|
||||
case CHANNEL_PROCESS_LOAD:
|
||||
PercentType processLoad = systeminfo.getProcessCpuUsage(deviceIndex);
|
||||
case CHANNEL_CURRENT_PROCESS_LOAD:
|
||||
DecimalType processLoad = processLoadCache.putIfAbsentAndGet(deviceIndex,
|
||||
() -> getProcessCpuUsage(deviceIndex));
|
||||
state = (processLoad != null) ? new QuantityType<>(processLoad, Units.PERCENT) : null;
|
||||
break;
|
||||
case CHANNEL_PROCESS_MEMORY:
|
||||
case CHANNEL_CURRENT_PROCESS_MEMORY:
|
||||
state = systeminfo.getProcessMemoryUsage(deviceIndex);
|
||||
break;
|
||||
case CHANNEL_PROCESS_NAME:
|
||||
case CHANNEL_CURRENT_PROCESS_NAME:
|
||||
state = systeminfo.getProcessName(deviceIndex);
|
||||
break;
|
||||
case CHANNEL_PROCESS_PATH:
|
||||
case CHANNEL_CURRENT_PROCESS_PATH:
|
||||
state = systeminfo.getProcessPath(deviceIndex);
|
||||
break;
|
||||
case CHANNEL_PROCESS_THREADS:
|
||||
case CHANNEL_CURRENT_PROCESS_THREADS:
|
||||
state = systeminfo.getProcessThreads(deviceIndex);
|
||||
break;
|
||||
default:
|
||||
logger.debug("Channel with unknown ID: {} !", channelID);
|
||||
}
|
||||
} catch (DeviceNotFoundException e) {
|
||||
logger.warn("No information for channel {} with device index {} :", channelID, deviceIndex);
|
||||
logger.warn("No information for channel {} with device index: {}", channelID, deviceIndex);
|
||||
} catch (Exception e) {
|
||||
logger.debug("Unexpected error occurred while getting system information!", e);
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||
"Cannot get system info as result of unexpected error. Please try to restart the binding (remove and re-add the thing)!");
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "@text/offline.unexpected-error");
|
||||
}
|
||||
return state != null ? state : UnDefType.UNDEF;
|
||||
}
|
||||
|
||||
private @Nullable PercentType getSystemCpuLoad() {
|
||||
return systeminfo.getSystemCpuLoad();
|
||||
}
|
||||
|
||||
private @Nullable DecimalType getProcessCpuUsage(int pid) {
|
||||
try {
|
||||
return systeminfo.getProcessCpuUsage(pid);
|
||||
} catch (DeviceNotFoundException e) {
|
||||
logger.warn("Process with pid {} does not exist", pid);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The device index is an optional part of the channelID - the last characters of the groupID. It is used to
|
||||
* identify unique device, when more than one devices are available (e.g. local disks with names C:\, D:\, E"\ - the
|
||||
|
@ -469,6 +645,7 @@ public class SysteminfoHandler extends BaseThingHandler {
|
|||
* @return natural number (number >=0)
|
||||
*/
|
||||
private int getDeviceIndex(ChannelUID channelUID) {
|
||||
String channelID = channelUID.getId();
|
||||
String channelGroupID = channelUID.getGroupId();
|
||||
if (channelGroupID == null) {
|
||||
return 0;
|
||||
|
@ -481,13 +658,23 @@ public class SysteminfoHandler extends BaseThingHandler {
|
|||
return pid;
|
||||
}
|
||||
|
||||
char lastChar = channelGroupID.charAt(channelGroupID.length() - 1);
|
||||
if (Character.isDigit(lastChar)) {
|
||||
// All non-digits are deleted from the ID
|
||||
if (channelGroupID.contains(CHANNEL_GROUP_CURRENT_PROCESS)) {
|
||||
int pid = systeminfo.getCurrentProcessID();
|
||||
return pid;
|
||||
}
|
||||
|
||||
// First try to get device index in group id, delete all non-digits from id
|
||||
if (Character.isDigit(channelGroupID.charAt(channelGroupID.length() - 1))) {
|
||||
String deviceIndexPart = channelGroupID.replaceAll("\\D+", "");
|
||||
return Integer.parseInt(deviceIndexPart);
|
||||
}
|
||||
|
||||
// If not found, try to find it in channel id, delete all non-digits from id
|
||||
if (Character.isDigit(channelID.charAt(channelID.length() - 1))) {
|
||||
String deviceIndexPart = channelID.replaceAll("\\D+", "");
|
||||
return Integer.parseInt(deviceIndexPart);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -510,10 +697,10 @@ public class SysteminfoHandler extends BaseThingHandler {
|
|||
pid = pidValue.intValue();
|
||||
}
|
||||
} else {
|
||||
logger.debug("Channel does not exist ! Fall back to default value.");
|
||||
logger.debug("Channel does not exist! Fall back to default value.");
|
||||
}
|
||||
} catch (ClassCastException e) {
|
||||
logger.debug("Channel configuration cannot be read ! Fall back to default value.", e);
|
||||
logger.debug("Channel configuration cannot be read! Fall back to default value.", e);
|
||||
} catch (IllegalArgumentException e) {
|
||||
logger.debug("PID (Process Identifier) must be positive number. Fall back to default value. ", e);
|
||||
}
|
||||
|
@ -524,10 +711,10 @@ public class SysteminfoHandler extends BaseThingHandler {
|
|||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
if (thing.getStatus().equals(ThingStatus.ONLINE)) {
|
||||
if (command instanceof RefreshType) {
|
||||
logger.debug("Refresh command received for channel {}!", channelUID);
|
||||
logger.debug("Refresh command received for channel {} !", channelUID);
|
||||
publishDataForChannel(channelUID);
|
||||
} else {
|
||||
logger.debug("Unsupported command {}! Supported commands: REFRESH", command);
|
||||
logger.debug("Unsupported command {} ! Supported commands: REFRESH", command);
|
||||
}
|
||||
} else {
|
||||
logger.debug("Cannot handle command. Thing is not ONLINE.");
|
||||
|
@ -546,9 +733,10 @@ public class SysteminfoHandler extends BaseThingHandler {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void thingUpdated(Thing thing) {
|
||||
logger.trace("About to update thing.");
|
||||
public synchronized void thingUpdated(Thing thing) {
|
||||
logger.trace("About to update thing");
|
||||
boolean isChannelConfigChanged = false;
|
||||
|
||||
List<Channel> channels = thing.getChannels();
|
||||
|
||||
for (Channel channel : channels) {
|
||||
|
@ -557,7 +745,7 @@ public class SysteminfoHandler extends BaseThingHandler {
|
|||
Channel oldChannel = this.thing.getChannel(channelUID.getId());
|
||||
|
||||
if (oldChannel == null) {
|
||||
logger.warn("Channel with UID {} cannot be updated, as it cannot be found !", channelUID);
|
||||
logger.warn("Channel with UID {} cannot be updated, as it cannot be found!", channelUID);
|
||||
continue;
|
||||
}
|
||||
Configuration currentChannelConfig = oldChannel.getConfiguration();
|
||||
|
@ -594,16 +782,22 @@ public class SysteminfoHandler extends BaseThingHandler {
|
|||
publishDataForChannel(channel.getUID());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void changeThingType(ThingTypeUID thingTypeUID, Configuration configuration) {
|
||||
storeChannelsConfig();
|
||||
super.changeThingType(thingTypeUID, configuration);
|
||||
}
|
||||
|
||||
private void stopScheduledUpdates() {
|
||||
ScheduledFuture<?> localHighPriorityTasks = highPriorityTasks;
|
||||
if (localHighPriorityTasks != null) {
|
||||
logger.debug("High prioriy tasks will not be run anymore !");
|
||||
logger.debug("High prioriy tasks will not be run anymore!");
|
||||
localHighPriorityTasks.cancel(true);
|
||||
}
|
||||
|
||||
ScheduledFuture<?> localMediumPriorityTasks = mediumPriorityTasks;
|
||||
if (localMediumPriorityTasks != null) {
|
||||
logger.debug("Medium prioriy tasks will not be run anymore !");
|
||||
logger.debug("Medium prioriy tasks will not be run anymore!");
|
||||
localMediumPriorityTasks.cancel(true);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -52,6 +52,7 @@ import oshi.util.EdidUtil;
|
|||
* @author Christoph Weitkamp - Update to OSHI 3.13.0 - Replaced deprecated method
|
||||
* CentralProcessor#getSystemSerialNumber()
|
||||
* @author Wouter Born - Update to OSHI 4.0.0 and add null annotations
|
||||
* @author Mark Herwege - Add dynamic creation of extra channels
|
||||
*
|
||||
* @see <a href="https://github.com/oshi/oshi">OSHI GitHub repository</a>
|
||||
*/
|
||||
|
@ -350,6 +351,8 @@ public class OSHISysteminfo implements SysteminfoInterface {
|
|||
int speed = 0; // 0 means unable to measure speed
|
||||
if (index < fanSpeeds.length) {
|
||||
speed = fanSpeeds[index];
|
||||
} else {
|
||||
throw new DeviceNotFoundException();
|
||||
}
|
||||
return speed > 0 ? new DecimalType(speed) : null;
|
||||
}
|
||||
|
@ -608,6 +611,11 @@ public class OSHISysteminfo implements SysteminfoInterface {
|
|||
return new DecimalType(getSizeInMB(bytesRecv));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCurrentProcessID() {
|
||||
return operatingSystem.getProcessId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable StringType getProcessName(int pid) throws DeviceNotFoundException {
|
||||
if (pid > 0) {
|
||||
|
@ -620,11 +628,11 @@ public class OSHISysteminfo implements SysteminfoInterface {
|
|||
}
|
||||
|
||||
@Override
|
||||
public @Nullable PercentType getProcessCpuUsage(int pid) throws DeviceNotFoundException {
|
||||
public @Nullable DecimalType getProcessCpuUsage(int pid) throws DeviceNotFoundException {
|
||||
if (pid > 0) {
|
||||
OSProcess process = getProcess(pid);
|
||||
PercentType load = (processTicks.containsKey(pid))
|
||||
? new PercentType(getPercentsValue(process.getProcessCpuLoadBetweenTicks(processTicks.get(pid))))
|
||||
DecimalType load = (processTicks.containsKey(pid))
|
||||
? new DecimalType(getPercentsValue(process.getProcessCpuLoadBetweenTicks(processTicks.get(pid))))
|
||||
: null;
|
||||
processTicks.put(pid, process);
|
||||
return load;
|
||||
|
@ -666,4 +674,34 @@ public class OSHISysteminfo implements SysteminfoInterface {
|
|||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getNetworkIFCount() {
|
||||
return networks.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDisplayCount() {
|
||||
return displays.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getFileOSStoreCount() {
|
||||
return fileStores.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPowerSourceCount() {
|
||||
return powerSources.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDriveCount() {
|
||||
return drives.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getFanCount() {
|
||||
return sensors.getFanSpeeds().length;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ import org.openhab.core.library.types.StringType;
|
|||
*
|
||||
* @author Svilen Valkanov - Initial contribution
|
||||
* @author Wouter Born - Add null annotations
|
||||
* @author Mark Herwege - Add dynamic creation of extra channels
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface SysteminfoInterface {
|
||||
|
@ -404,6 +405,13 @@ public interface SysteminfoInterface {
|
|||
*/
|
||||
public StringType getBatteryName(int deviceIndex) throws DeviceNotFoundException;
|
||||
|
||||
/**
|
||||
* Get PID of process executing this code
|
||||
*
|
||||
* @return current process ID
|
||||
*/
|
||||
int getCurrentProcessID();
|
||||
|
||||
/**
|
||||
* Returns the name of the process
|
||||
*
|
||||
|
@ -416,10 +424,10 @@ public interface SysteminfoInterface {
|
|||
* Returns the CPU usage of the process
|
||||
*
|
||||
* @param pid - the PID of the process
|
||||
* @return - percentage value /0-100/
|
||||
* @return - percentage value, can be above 100% if process uses multiple cores
|
||||
* @throws DeviceNotFoundException - thrown if process with this PID can not be found
|
||||
*/
|
||||
public @Nullable PercentType getProcessCpuUsage(int pid) throws DeviceNotFoundException;
|
||||
public @Nullable DecimalType getProcessCpuUsage(int pid) throws DeviceNotFoundException;
|
||||
|
||||
/**
|
||||
* Returns the size of RAM memory only usage of the process
|
||||
|
@ -445,4 +453,46 @@ public interface SysteminfoInterface {
|
|||
* @throws DeviceNotFoundException - thrown if process with this PID can not be found
|
||||
*/
|
||||
public @Nullable DecimalType getProcessThreads(int pid) throws DeviceNotFoundException;
|
||||
|
||||
/**
|
||||
* Returns the number of network interfaces.
|
||||
*
|
||||
* @return network interface count
|
||||
*/
|
||||
public int getNetworkIFCount();
|
||||
|
||||
/**
|
||||
* Returns the number of displays.
|
||||
*
|
||||
* @return display count
|
||||
*/
|
||||
public int getDisplayCount();
|
||||
|
||||
/**
|
||||
* Returns the number of storages.
|
||||
*
|
||||
* @return storage count
|
||||
*/
|
||||
public int getFileOSStoreCount();
|
||||
|
||||
/**
|
||||
* Returns the number of power sources/batteries.
|
||||
*
|
||||
* @return power source count
|
||||
*/
|
||||
public int getPowerSourceCount();
|
||||
|
||||
/**
|
||||
* Returns the number of drives.
|
||||
*
|
||||
* @return drive count
|
||||
*/
|
||||
public int getDriveCount();
|
||||
|
||||
/**
|
||||
* Returns the number of fans.
|
||||
*
|
||||
* @return fan count
|
||||
*/
|
||||
int getFanCount();
|
||||
}
|
||||
|
|
|
@ -29,6 +29,8 @@ channel-group-type.systeminfo.memoryGroup.label = Physical Memory
|
|||
channel-group-type.systeminfo.memoryGroup.description = Physical memory information
|
||||
channel-group-type.systeminfo.networkGroup.label = Network
|
||||
channel-group-type.systeminfo.networkGroup.description = Network parameters
|
||||
channel-group-type.systeminfo.currentProcessGroup.label = Current Process
|
||||
channel-group-type.systeminfo.currentProcessGroup.description = Current process information
|
||||
channel-group-type.systeminfo.processGroup.label = Process
|
||||
channel-group-type.systeminfo.processGroup.description = System process information
|
||||
channel-group-type.systeminfo.sensorsGroup.label = Sensor
|
||||
|
@ -62,8 +64,8 @@ channel-type.systeminfo.information.label = Display Information
|
|||
channel-type.systeminfo.information.description = Product, manufacturer, SN, width and height of the display in cm
|
||||
channel-type.systeminfo.ip.label = IP Address
|
||||
channel-type.systeminfo.ip.description = Host IP address of the network
|
||||
channel-type.systeminfo.cpuLoad.label = CPU Load
|
||||
channel-type.systeminfo.cpuLoad.description = CPU load in percent
|
||||
channel-type.systeminfo.load.label = Load
|
||||
channel-type.systeminfo.load.description = Load in percent
|
||||
channel-type.systeminfo.loadAverage.label = Load Average
|
||||
channel-type.systeminfo.loadAverage.description = Load as a number of processes for the last 1,5 or 15 minutes
|
||||
channel-type.systeminfo.load_process.label = Load
|
||||
|
@ -84,6 +86,8 @@ channel-type.systeminfo.packetsReceived.label = Packets Received
|
|||
channel-type.systeminfo.packetsReceived.description = Number of packets received
|
||||
channel-type.systeminfo.packetsSent.label = Packets Sent
|
||||
channel-type.systeminfo.packetsSent.description = Number of packets sent
|
||||
channel-type.systeminfo.path.label = Path
|
||||
channel-type.systeminfo.path.description = The full path
|
||||
channel-type.systeminfo.path_process.label = Path
|
||||
channel-type.systeminfo.path_process.description = The full path
|
||||
channel-type.systeminfo.remainingCapacity.label = Remaining Capacity
|
||||
|
@ -151,3 +155,7 @@ channel-type.config.systeminfo.mediumpriority_process.priority.description = Ref
|
|||
channel-type.config.systeminfo.mediumpriority_process.priority.option.High = High
|
||||
channel-type.config.systeminfo.mediumpriority_process.priority.option.Medium = Medium
|
||||
channel-type.config.systeminfo.mediumpriority_process.priority.option.Low = Low
|
||||
|
||||
# thing status messages
|
||||
offline.cannot-initialize = Thing cannot be initialized!
|
||||
offline.unexpected-error = Cannot get system info as result of unexpected error. Please try to restart the binding (remove and re-add the thing)!
|
||||
|
|
|
@ -107,7 +107,7 @@
|
|||
<channels>
|
||||
<channel id="name" typeId="name"/>
|
||||
<channel id="description" typeId="description"/>
|
||||
<channel id="load" typeId="cpuLoad"/>
|
||||
<channel id="load" typeId="load"/>
|
||||
<channel id="load1" typeId="loadAverage"/>
|
||||
<channel id="load5" typeId="loadAverage"/>
|
||||
<channel id="load15" typeId="loadAverage"/>
|
||||
|
@ -116,6 +116,18 @@
|
|||
</channels>
|
||||
</channel-group-type>
|
||||
|
||||
<channel-group-type id="currentProcessGroup">
|
||||
<label>Current Process</label>
|
||||
<description>Current process information</description>
|
||||
<channels>
|
||||
<channel id="load" typeId="load"/>
|
||||
<channel id="used" typeId="used"/>
|
||||
<channel id="name" typeId="name"/>
|
||||
<channel id="threads" typeId="threads"/>
|
||||
<channel id="path" typeId="path"/>
|
||||
</channels>
|
||||
</channel-group-type>
|
||||
|
||||
<channel-group-type id="processGroup">
|
||||
<label>Process</label>
|
||||
<description>System process information</description>
|
||||
|
@ -144,6 +156,14 @@
|
|||
<config-description-ref uri="channel-type:systeminfo:mediumpriority"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="path">
|
||||
<item-type>String</item-type>
|
||||
<label>Path</label>
|
||||
<description>The full path</description>
|
||||
<state readOnly="true" pattern="%s"/>
|
||||
<config-description-ref uri="channel-type:systeminfo:lowpriority"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="path_process">
|
||||
<item-type>String</item-type>
|
||||
<label>Path</label>
|
||||
|
@ -288,6 +308,14 @@
|
|||
<config-description-ref uri="channel-type:systeminfo:mediumpriority"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="load">
|
||||
<item-type>Number:Dimensionless</item-type>
|
||||
<label>Load</label>
|
||||
<description>Load in percent</description>
|
||||
<state readOnly="true" pattern="%.1f %%"/>
|
||||
<config-description-ref uri="channel-type:systeminfo:highpriority"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="load_process">
|
||||
<item-type>Number:Dimensionless</item-type>
|
||||
<label>Load</label>
|
||||
|
@ -296,14 +324,6 @@
|
|||
<config-description-ref uri="channel-type:systeminfo:highpriority_process"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="cpuLoad">
|
||||
<item-type>Number:Dimensionless</item-type>
|
||||
<label>CPU Load</label>
|
||||
<description>CPU load in percent</description>
|
||||
<state readOnly="true" pattern="%.1f %%"/>
|
||||
<config-description-ref uri="channel-type:systeminfo:highpriority"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="loadAverage" advanced="true">
|
||||
<item-type>Number</item-type>
|
||||
<label>Load Average</label>
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
<channel-group id="storage" typeId="storageGroup"/>
|
||||
<channel-group id="sensors" typeId="sensorsGroup"/>
|
||||
<channel-group id="cpu" typeId="cpuGroup"/>
|
||||
<!-- This group types are not mandatory for every computer configuration -->
|
||||
<channel-group id="currentProcess" typeId="currentProcessGroup"/>
|
||||
<channel-group id="process" typeId="processGroup"/>
|
||||
<channel-group id="drive" typeId="driveGroup"/>
|
||||
<channel-group id="swap" typeId="swapGroup"/>
|
||||
|
|
|
@ -74,4 +74,6 @@ Fragment-Host: org.openhab.binding.systeminfo
|
|||
org.openhab.core.io.console;version='[3.4.0,3.4.1)',\
|
||||
org.openhab.core.test;version='[3.4.0,3.4.1)',\
|
||||
org.openhab.core.thing;version='[3.4.0,3.4.1)',\
|
||||
org.openhab.core.thing.xml;version='[3.4.0,3.4.1)'
|
||||
org.openhab.core.thing.xml;version='[3.4.0,3.4.1)',\
|
||||
org.mockito.junit-jupiter;version='[4.1.0,4.1.1)',\
|
||||
com.github.oshi.oshi-core;version='[6.2.2,6.2.3)'
|
||||
|
|
|
@ -24,15 +24,21 @@ import java.net.UnknownHostException;
|
|||
import java.util.Hashtable;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.openhab.binding.systeminfo.internal.SysteminfoBindingConstants;
|
||||
import org.openhab.binding.systeminfo.internal.SysteminfoHandlerFactory;
|
||||
import org.openhab.binding.systeminfo.internal.SysteminfoThingTypeProvider;
|
||||
import org.openhab.binding.systeminfo.internal.discovery.SysteminfoDiscoveryService;
|
||||
import org.openhab.binding.systeminfo.internal.handler.SysteminfoHandler;
|
||||
import org.openhab.binding.systeminfo.internal.model.DeviceNotFoundException;
|
||||
import org.openhab.binding.systeminfo.internal.model.OSHISysteminfo;
|
||||
import org.openhab.binding.systeminfo.internal.model.SysteminfoInterface;
|
||||
import org.openhab.core.config.core.Configuration;
|
||||
import org.openhab.core.config.discovery.DiscoveryResult;
|
||||
|
@ -61,6 +67,7 @@ import org.openhab.core.thing.ThingTypeUID;
|
|||
import org.openhab.core.thing.ThingUID;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandlerFactory;
|
||||
import org.openhab.core.thing.binding.ThingTypeProvider;
|
||||
import org.openhab.core.thing.binding.builder.ChannelBuilder;
|
||||
import org.openhab.core.thing.binding.builder.ThingBuilder;
|
||||
import org.openhab.core.thing.link.ItemChannelLink;
|
||||
|
@ -78,6 +85,8 @@ import org.openhab.core.types.UnDefType;
|
|||
* but mock data will be used instead, avoiding potential errors from the OS queries.
|
||||
* @author Wouter Born - Migrate Groovy to Java tests
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
public class SysteminfoOSGiTest extends JavaOSGiTest {
|
||||
private static final String DEFAULT_TEST_THING_NAME = "work";
|
||||
private static final String DEFAULT_TEST_ITEM_NAME = "test";
|
||||
|
@ -97,15 +106,13 @@ public class SysteminfoOSGiTest extends JavaOSGiTest {
|
|||
*/
|
||||
private static final int DEFAULT_TEST_INTERVAL_MEDIUM = 3;
|
||||
|
||||
private Thing systemInfoThing;
|
||||
private SysteminfoHandler systemInfoHandler;
|
||||
private GenericItem testItem;
|
||||
private @Nullable Thing systemInfoThing;
|
||||
private @Nullable GenericItem testItem;
|
||||
|
||||
private SysteminfoInterface mockedSystemInfo;
|
||||
private ManagedThingProvider managedThingProvider;
|
||||
private ThingRegistry thingRegistry;
|
||||
private ItemRegistry itemRegistry;
|
||||
private SysteminfoHandlerFactory systeminfoHandlerFactory;
|
||||
private @Mock @NonNullByDefault({}) OSHISysteminfo mockedSystemInfo;
|
||||
private @NonNullByDefault({}) SysteminfoHandlerFactory systeminfoHandlerFactory;
|
||||
private @NonNullByDefault({}) ThingRegistry thingRegistry;
|
||||
private @NonNullByDefault({}) ItemRegistry itemRegistry;
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
|
@ -113,48 +120,75 @@ public class SysteminfoOSGiTest extends JavaOSGiTest {
|
|||
registerService(volatileStorageService);
|
||||
|
||||
// Preparing the mock with OS properties, that are used in the initialize method of SysteminfoHandler
|
||||
mockedSystemInfo = mock(SysteminfoInterface.class);
|
||||
when(mockedSystemInfo.getCpuLogicalCores()).thenReturn(new DecimalType(2));
|
||||
when(mockedSystemInfo.getCpuPhysicalCores()).thenReturn(new DecimalType(2));
|
||||
when(mockedSystemInfo.getOsFamily()).thenReturn(new StringType("Mock OS"));
|
||||
when(mockedSystemInfo.getOsManufacturer()).thenReturn(new StringType("Mock OS Manufacturer"));
|
||||
when(mockedSystemInfo.getOsVersion()).thenReturn(new StringType("Mock Os Version"));
|
||||
// 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.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"));
|
||||
// Following mock method returns will make sure the thing does not get recreated with extra channels
|
||||
lenient().when(mockedSystemInfo.getNetworkIFCount()).thenReturn(1);
|
||||
lenient().when(mockedSystemInfo.getDisplayCount()).thenReturn(1);
|
||||
lenient().when(mockedSystemInfo.getFileOSStoreCount()).thenReturn(1);
|
||||
lenient().when(mockedSystemInfo.getPowerSourceCount()).thenReturn(1);
|
||||
lenient().when(mockedSystemInfo.getDriveCount()).thenReturn(1);
|
||||
lenient().when(mockedSystemInfo.getFanCount()).thenReturn(1);
|
||||
|
||||
registerService(mockedSystemInfo);
|
||||
|
||||
waitForAssert(() -> {
|
||||
systeminfoHandlerFactory = getService(ThingHandlerFactory.class, SysteminfoHandlerFactory.class);
|
||||
assertThat(systeminfoHandlerFactory, is(notNullValue()));
|
||||
});
|
||||
if (systeminfoHandlerFactory != null) {
|
||||
// Unbind oshiSystemInfo service and bind the mock service to make the systeminfo binding tests independent
|
||||
// of the external OSHI library
|
||||
SysteminfoInterface oshiSystemInfo = getService(SysteminfoInterface.class);
|
||||
|
||||
// Unbind oshiSystemInfo service and bind the mock service to make the systeminfobinding tests independent of
|
||||
// the external OSHI library
|
||||
if (oshiSystemInfo != null) {
|
||||
systeminfoHandlerFactory.unbindSystemInfo(oshiSystemInfo);
|
||||
}
|
||||
systeminfoHandlerFactory.bindSystemInfo(mockedSystemInfo);
|
||||
}
|
||||
|
||||
managedThingProvider = getService(ThingProvider.class, ManagedThingProvider.class);
|
||||
assertThat(managedThingProvider, is(notNullValue()));
|
||||
waitForAssert(() -> {
|
||||
ThingTypeProvider thingTypeProvider = getService(ThingTypeProvider.class,
|
||||
SysteminfoThingTypeProvider.class);
|
||||
assertThat(thingTypeProvider, is(notNullValue()));
|
||||
});
|
||||
waitForAssert(() -> {
|
||||
SysteminfoThingTypeProvider systeminfoThingTypeProvider = getService(SysteminfoThingTypeProvider.class);
|
||||
assertThat(systeminfoThingTypeProvider, is(notNullValue()));
|
||||
});
|
||||
|
||||
waitForAssert(() -> {
|
||||
thingRegistry = getService(ThingRegistry.class);
|
||||
assertThat(thingRegistry, is(notNullValue()));
|
||||
});
|
||||
|
||||
waitForAssert(() -> {
|
||||
itemRegistry = getService(ItemRegistry.class);
|
||||
assertThat(itemRegistry, is(notNullValue()));
|
||||
});
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void tearDown() {
|
||||
if (systemInfoThing != null) {
|
||||
Thing thing = systemInfoThing;
|
||||
if (thing != null) {
|
||||
// Remove the systeminfo thing. The handler will be also disposed automatically
|
||||
Thing removedThing = thingRegistry.forceRemove(systemInfoThing.getUID());
|
||||
Thing removedThing = thingRegistry.forceRemove(thing.getUID());
|
||||
assertThat("The systeminfo thing cannot be deleted", removedThing, is(notNullValue()));
|
||||
}
|
||||
waitForAssert(() -> {
|
||||
ThingHandler systemInfoHandler = systemInfoThing.getHandler();
|
||||
ThingHandler systemInfoHandler = thing.getHandler();
|
||||
assertThat(systemInfoHandler, is(nullValue()));
|
||||
});
|
||||
}
|
||||
|
||||
if (testItem != null) {
|
||||
itemRegistry.remove(DEFAULT_TEST_ITEM_NAME);
|
||||
}
|
||||
|
||||
unregisterService(mockedSystemInfo);
|
||||
}
|
||||
|
||||
private void initializeThingWithChannelAndPID(String channelID, String acceptedItemType, int pid) {
|
||||
|
@ -214,18 +248,24 @@ public class SysteminfoOSGiTest extends JavaOSGiTest {
|
|||
Channel channel = ChannelBuilder.create(channelUID, acceptedItemType).withType(channelTypeUID)
|
||||
.withKind(ChannelKind.STATE).withConfiguration(channelConfig).build();
|
||||
|
||||
systemInfoThing = ThingBuilder.create(thingTypeUID, thingUID).withConfiguration(thingConfiguration)
|
||||
Thing thing = ThingBuilder.create(thingTypeUID, thingUID).withConfiguration(thingConfiguration)
|
||||
.withChannel(channel).build();
|
||||
systemInfoThing = thing;
|
||||
|
||||
managedThingProvider.add(systemInfoThing);
|
||||
ManagedThingProvider managedThingProvider = getService(ThingProvider.class, ManagedThingProvider.class);
|
||||
assertThat(managedThingProvider, is(notNullValue()));
|
||||
|
||||
if (managedThingProvider != null) {
|
||||
managedThingProvider.add(thing);
|
||||
}
|
||||
|
||||
waitForAssert(() -> {
|
||||
systemInfoHandler = (SysteminfoHandler) systemInfoThing.getHandler();
|
||||
assertThat(systemInfoHandler, is(notNullValue()));
|
||||
SysteminfoHandler handler = (SysteminfoHandler) thing.getHandler();
|
||||
assertThat(handler, is(notNullValue()));
|
||||
});
|
||||
|
||||
waitForAssert(() -> {
|
||||
assertThat("Thing is not initilized, before an Item is created", systemInfoThing.getStatus(),
|
||||
assertThat("Thing is not initialized, before an Item is created", thing.getStatus(),
|
||||
anyOf(equalTo(ThingStatus.OFFLINE), equalTo(ThingStatus.ONLINE)));
|
||||
});
|
||||
|
||||
|
@ -233,11 +273,15 @@ public class SysteminfoOSGiTest extends JavaOSGiTest {
|
|||
}
|
||||
|
||||
private void assertItemState(String acceptedItemType, String itemName, String priority, State expectedState) {
|
||||
Thing thing = systemInfoThing;
|
||||
if (thing == null) {
|
||||
throw new AssertionError("Thing is null");
|
||||
}
|
||||
waitForAssert(() -> {
|
||||
ThingStatusDetail thingStatusDetail = systemInfoThing.getStatusInfo().getStatusDetail();
|
||||
String description = systemInfoThing.getStatusInfo().getDescription();
|
||||
ThingStatusDetail thingStatusDetail = thing.getStatusInfo().getStatusDetail();
|
||||
String description = thing.getStatusInfo().getDescription();
|
||||
assertThat("Thing status detail is " + thingStatusDetail + " with description " + description,
|
||||
systemInfoThing.getStatus(), is(equalTo(ThingStatus.ONLINE)));
|
||||
thing.getStatus(), is(equalTo(ThingStatus.ONLINE)));
|
||||
});
|
||||
// The binding starts all refresh tasks in SysteminfoHandler.scheduleUpdates() after this delay !
|
||||
try {
|
||||
|
@ -254,9 +298,9 @@ public class SysteminfoOSGiTest extends JavaOSGiTest {
|
|||
}
|
||||
|
||||
int waitTime;
|
||||
if (priority.equals("High")) {
|
||||
if ("High".equals(priority)) {
|
||||
waitTime = DEFAULT_TEST_INTERVAL_HIGH * 1000;
|
||||
} else if (priority.equals("Medium")) {
|
||||
} else if ("Medium".equals(priority)) {
|
||||
waitTime = DEFAULT_TEST_INTERVAL_MEDIUM * 1000;
|
||||
} else {
|
||||
waitTime = 100;
|
||||
|
@ -269,16 +313,25 @@ public class SysteminfoOSGiTest extends JavaOSGiTest {
|
|||
}
|
||||
|
||||
private void intializeItem(ChannelUID channelUID, String itemName, String acceptedItemType) {
|
||||
if (acceptedItemType.equals("Number")) {
|
||||
testItem = new NumberItem(itemName);
|
||||
} else if (acceptedItemType.equals("String")) {
|
||||
testItem = new StringItem(itemName);
|
||||
GenericItem item = null;
|
||||
if ("Number".equals(acceptedItemType)) {
|
||||
item = new NumberItem(itemName);
|
||||
} else if ("String".equals(acceptedItemType)) {
|
||||
item = new StringItem(itemName);
|
||||
}
|
||||
itemRegistry.add(testItem);
|
||||
if (item == null) {
|
||||
throw new AssertionError("Item is null");
|
||||
}
|
||||
itemRegistry.add(item);
|
||||
testItem = item;
|
||||
|
||||
ManagedItemChannelLinkProvider itemChannelLinkProvider = getService(ManagedItemChannelLinkProvider.class);
|
||||
assertThat(itemChannelLinkProvider, is(notNullValue()));
|
||||
|
||||
if (itemChannelLinkProvider == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
itemChannelLinkProvider.add(new ItemChannelLink(itemName, channelUID));
|
||||
}
|
||||
|
||||
|
@ -300,29 +353,13 @@ public class SysteminfoOSGiTest extends JavaOSGiTest {
|
|||
|
||||
private void testInvalidConfiguration() {
|
||||
waitForAssert(() -> {
|
||||
assertThat("Invalid configuratuin is used !", systemInfoThing.getStatus(),
|
||||
is(equalTo(ThingStatus.OFFLINE)));
|
||||
assertThat(systemInfoThing.getStatusInfo().getStatusDetail(),
|
||||
Thing thing = systemInfoThing;
|
||||
if (thing != null) {
|
||||
assertThat("Invalid configuration is used !", thing.getStatus(), is(equalTo(ThingStatus.OFFLINE)));
|
||||
assertThat(thing.getStatusInfo().getStatusDetail(),
|
||||
is(equalTo(ThingStatusDetail.HANDLER_INITIALIZING_ERROR)));
|
||||
assertThat(systemInfoThing.getStatusInfo().getDescription(), is(equalTo("Thing cannot be initialized!")));
|
||||
});
|
||||
assertThat(thing.getStatusInfo().getDescription(), is(equalTo("@text/offline.cannot-initialize")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void assertThingStatusIsUninitializedWhenThereIsNoSysteminfoServiceProvided() {
|
||||
// Unbind the mock service to verify the systeminfo thing will not be initialized when no systeminfo service is
|
||||
// provided
|
||||
systeminfoHandlerFactory.unbindSystemInfo(mockedSystemInfo);
|
||||
|
||||
ThingTypeUID thingTypeUID = SysteminfoBindingConstants.THING_TYPE_COMPUTER;
|
||||
ThingUID thingUID = new ThingUID(thingTypeUID, DEFAULT_TEST_THING_NAME);
|
||||
|
||||
systemInfoThing = ThingBuilder.create(thingTypeUID, thingUID).build();
|
||||
managedThingProvider.add(systemInfoThing);
|
||||
|
||||
waitForAssert(() -> {
|
||||
assertThat("The thing status is uninitialized when systeminfo service is missing",
|
||||
systemInfoThing.getStatus(), equalTo(ThingStatus.UNINITIALIZED));
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -672,7 +709,7 @@ public class SysteminfoOSGiTest extends JavaOSGiTest {
|
|||
mockedDriveSerialNumber);
|
||||
}
|
||||
|
||||
@Disabled
|
||||
// Re-enable this previously disabled test, as it is not relying on hardware anymore, but a mocked object
|
||||
// There is a bug opened for this issue - https://github.com/dblock/oshi/issues/185
|
||||
@Test
|
||||
public void assertChannelSensorsCpuTempIsUpdated() {
|
||||
|
@ -874,7 +911,7 @@ public class SysteminfoOSGiTest extends JavaOSGiTest {
|
|||
|
||||
@Override
|
||||
protected String getHostName() throws UnknownHostException {
|
||||
if (hostname.equals("unresolved")) {
|
||||
if ("unresolved".equals(hostname)) {
|
||||
throw new UnknownHostException();
|
||||
}
|
||||
return hostname;
|
||||
|
@ -938,6 +975,10 @@ public class SysteminfoOSGiTest extends JavaOSGiTest {
|
|||
Inbox inbox = getService(Inbox.class);
|
||||
assertThat(inbox, is(notNullValue()));
|
||||
|
||||
if (inbox == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
waitForAssert(() -> {
|
||||
List<DiscoveryResult> results = inbox.stream().filter(InboxPredicates.forThingUID(computerUID))
|
||||
.collect(toList());
|
||||
|
@ -951,8 +992,13 @@ public class SysteminfoOSGiTest extends JavaOSGiTest {
|
|||
assertThat(systemInfoThing, is(notNullValue()));
|
||||
});
|
||||
|
||||
Thing thing = systemInfoThing;
|
||||
if (thing == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
waitForAssert(() -> {
|
||||
assertThat("Thing is not initialized.", systemInfoThing.getStatus(), is(equalTo(ThingStatus.ONLINE)));
|
||||
assertThat("Thing is not initialized.", thing.getStatus(), is(equalTo(ThingStatus.ONLINE)));
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -1020,7 +1066,7 @@ public class SysteminfoOSGiTest extends JavaOSGiTest {
|
|||
// The pid of the System idle process in Windows
|
||||
int pid = 0;
|
||||
|
||||
PercentType mockedProcessLoad = new PercentType(3);
|
||||
DecimalType mockedProcessLoad = new DecimalType(3);
|
||||
when(mockedSystemInfo.getProcessCpuUsage(pid)).thenReturn(mockedProcessLoad);
|
||||
|
||||
initializeThingWithChannelAndPID(channnelID, acceptedItemType, pid);
|
||||
|
@ -1037,15 +1083,27 @@ public class SysteminfoOSGiTest extends JavaOSGiTest {
|
|||
String acceptedItemType = "Number";
|
||||
initializeThingWithChannel(DEFAULT_TEST_CHANNEL_ID, acceptedItemType);
|
||||
|
||||
Channel channel = systemInfoThing.getChannel(DEFAULT_TEST_CHANNEL_ID);
|
||||
Thing thing = systemInfoThing;
|
||||
if (thing == null) {
|
||||
throw new AssertionError("Thing is null");
|
||||
}
|
||||
Channel channel = thing.getChannel(DEFAULT_TEST_CHANNEL_ID);
|
||||
if (channel == null) {
|
||||
throw new AssertionError("Channel '" + DEFAULT_TEST_CHANNEL_ID + "' is null");
|
||||
}
|
||||
|
||||
ThingHandler thingHandler = thing.getHandler();
|
||||
if (thingHandler == null) {
|
||||
throw new AssertionError("Thing handler is null");
|
||||
}
|
||||
if (!(thingHandler.getClass().equals(SysteminfoHandler.class))) {
|
||||
throw new AssertionError("Thing handler not of class SysteminfoHandler");
|
||||
}
|
||||
SysteminfoHandler handler = (SysteminfoHandler) thingHandler;
|
||||
waitForAssert(() -> {
|
||||
assertThat("The initial priority of channel " + channel.getUID() + " is not as expected.",
|
||||
channel.getConfiguration().get(priorityKey), is(equalTo(initialPriority)));
|
||||
assertThat(systemInfoHandler.getHighPriorityChannels().contains(channel.getUID()), is(true));
|
||||
assertThat(handler.getHighPriorityChannels().contains(channel.getUID()), is(true));
|
||||
});
|
||||
|
||||
// Change the priority of a channel, keep the pid
|
||||
|
@ -1056,15 +1114,15 @@ public class SysteminfoOSGiTest extends JavaOSGiTest {
|
|||
.withType(channel.getChannelTypeUID()).withKind(channel.getKind()).withConfiguration(updatedConfig)
|
||||
.build();
|
||||
|
||||
Thing updatedThing = ThingBuilder.create(systemInfoThing.getThingTypeUID(), systemInfoThing.getUID())
|
||||
.withConfiguration(systemInfoThing.getConfiguration()).withChannel(updatedChannel).build();
|
||||
Thing updatedThing = ThingBuilder.create(thing.getThingTypeUID(), thing.getUID())
|
||||
.withConfiguration(thing.getConfiguration()).withChannel(updatedChannel).build();
|
||||
|
||||
systemInfoHandler.thingUpdated(updatedThing);
|
||||
handler.thingUpdated(updatedThing);
|
||||
|
||||
waitForAssert(() -> {
|
||||
assertThat("The prority of the channel was not updated: ", channel.getConfiguration().get(priorityKey),
|
||||
is(equalTo(newPriority)));
|
||||
assertThat(systemInfoHandler.getLowPriorityChannels().contains(channel.getUID()), is(true));
|
||||
assertThat(handler.getLowPriorityChannels().contains(channel.getUID()), is(true));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue