[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:
Mark Herwege
2022-11-04 13:28:27 +01:00
committed by GitHub
parent a4f6159f09
commit cf2a1afd56
12 changed files with 975 additions and 160 deletions

View File

@@ -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
*/

View File

@@ -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;
}
}

View File

@@ -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();
}
}

View File

@@ -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.systeminfo = systeminfo;
} else {
throw new IllegalArgumentException("No systeminfo service was provided");
}
this.thingTypeProvider = thingTypeProvider;
this.systeminfo = systeminfo;
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()) {
groupChannelsByPriority();
scheduleUpdates();
updateStatus(ThingStatus.ONLINE);
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);
}
}

View File

@@ -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;
}
}

View File

@@ -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();
}

View File

@@ -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)!

View File

@@ -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>

View File

@@ -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"/>