From: Alexander Falkenstern Date: Fri, 31 May 2024 08:11:51 +0000 (+0200) Subject: [systeminfo] Rename Systeminfo* to SystemInfo* (#16823) X-Git-Url: https://git.basschouten.com/?a=commitdiff_plain;h=c8365610efc47ecae720c1ee9eef22bc74ae5725;p=openhab-addons.git [systeminfo] Rename Systeminfo* to SystemInfo* (#16823) Signed-off-by: Alexander Falkenstern --- diff --git a/bundles/org.openhab.binding.systeminfo/pom.xml b/bundles/org.openhab.binding.systeminfo/pom.xml index 6e8f2d00c9..2e16481931 100644 --- a/bundles/org.openhab.binding.systeminfo/pom.xml +++ b/bundles/org.openhab.binding.systeminfo/pom.xml @@ -12,7 +12,7 @@ org.openhab.binding.systeminfo - openHAB Add-ons :: Bundles :: Systeminfo Binding + openHAB Add-ons :: Bundles :: SystemInfo Binding diff --git a/bundles/org.openhab.binding.systeminfo/src/main/java/org/openhab/binding/systeminfo/internal/SystemInfoBindingConstants.java b/bundles/org.openhab.binding.systeminfo/src/main/java/org/openhab/binding/systeminfo/internal/SystemInfoBindingConstants.java new file mode 100644 index 0000000000..71a2e8df2e --- /dev/null +++ b/bundles/org.openhab.binding.systeminfo/src/main/java/org/openhab/binding/systeminfo/internal/SystemInfoBindingConstants.java @@ -0,0 +1,487 @@ +/** + * Copyright (c) 2010-2024 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 org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.core.thing.ThingTypeUID; + +/** + * The {@link SystemInfoBindingConstants} class defines common constants, which are + * used across the whole binding. + * + * @author Svilen Valkanov - Initial contribution + * @author Mark Herwege - Add dynamic creation of extra channels + * @author Mark Herwege - Processor frequency channels + */ +@NonNullByDefault +public class SystemInfoBindingConstants { + + public static final String BINDING_ID = "systeminfo"; + + 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 + /** + * Number of CPU logical cores + */ + public static final String PROPERTY_CPU_LOGICAL_CORES = "CPU Logical Cores"; + + /** + * Number of CPU physical cores + */ + public static final String PROPERTY_CPU_PHYSICAL_CORES = "CPU Physical Cores"; + + /** + * Contains information about the family /Windows, Linux, OS X etc/ of the operation system + */ + public static final String PROPERTY_OS_FAMILY = "OS Family"; + + /** + * Name of the manufacturer of the operation system + */ + public static final String PROPERTY_OS_MANUFACTURER = "OS Manufacturer"; + + /** + * Version of the operation system + */ + public static final String PROPERTY_OS_VERSION = "OS Version"; + + // 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 + */ + public static final String CHANNEL_MEMORY_AVAILABLE = "memory#available"; + + /** + * Size of the used memory + */ + public static final String CHANNEL_MEMORY_USED = "memory#used"; + + /** + * Total size of the memory + */ + public static final String CHANNEL_MEMORY_TOTAL = "memory#total"; + + /** + * Percents of the available memory + */ + public static final String CHANNEL_MEMORY_AVAILABLE_PERCENT = "memory#availablePercent"; + + /** + * Percents of the used memory + */ + public static final String CHANNEL_MEMORY_USED_PERCENT = "memory#usedPercent"; + + /** + * Percents of the used heap + */ + public static final String CHANNEL_MEMORY_USED_HEAP_PERCENT = "memory#usedHeapPercent"; + + /** + * Bytes used in the heap + */ + 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 + */ + public static final String CHANNEL_SWAP_TOTAL = "swap#total"; + + /** + * Size of the available swap memory + */ + public static final String CHANNEL_SWAP_AVAILABLE = "swap#available"; + + /** + * Size of the used swap memory + */ + public static final String CHANNEL_SWAP_USED = "swap#used"; + + /** + * Percents of the available swap memory + */ + public static final String CHANNEL_SWAP_AVAILABLE_PERCENT = "swap#availablePercent"; + + /** + * Percents of the used swap memory + */ + 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 + */ + public static final String CHANNEL_DRIVE_NAME = "drive#name"; + + /** + * Physical storage drive model + */ + public static final String CHANNEL_DRIVE_MODEL = "drive#model"; + + /** + * Physical storage drive serial number + */ + 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 + */ + public static final String CHANNEL_STORAGE_NAME = "storage#name"; + + /** + * Logical storage volume type -(e.g. NTFS, FAT32 ..) + */ + public static final String CHANNEL_STORAGE_TYPE = "storage#type"; + + /** + * Description of the logical volume storage + */ + public static final String CHANNEL_STORAGE_DESCRIPTION = "storage#description"; + + /** + * Size of the available storage space + */ + public static final String CHANNEL_STORAGE_AVAILABLE = "storage#available"; + + /** + * Size of the used storage space + */ + public static final String CHANNEL_STORAGE_USED = "storage#used"; + + /** + * Total storage space + */ + public static final String CHANNEL_STORAGE_TOTAL = "storage#total"; + + /** + * Percents of the available storage space + */ + public static final String CHANNEL_STORAGE_AVAILABLE_PERCENT = "storage#availablePercent"; + + /** + * Percents of the used storage space + */ + 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. + */ + public static final String CHANNEL_SENSORS_CPU_TEMPERATURE = "sensors#cpuTemp"; + + /** + * Voltage of the CPU core. + */ + public static final String CHANNEL_SENOSRS_CPU_VOLTAGE = "sensors#cpuVoltage"; + + /** + * Fan speed + */ + 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 + */ + public static final String CHANNEL_BATTERY_NAME = "battery#name"; + + /** + * Remaining capacity of the battery. + */ + public static final String CHANNEL_BATTERY_REMAINING_CAPACITY = "battery#remainingCapacity"; + + /** + * Estimated remaining time of the battery + */ + 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 + */ + public static final String CHANNEL_CPU_DESCRIPTION = "cpu#description"; + + /** + * Maximum frequency of the CPU + */ + public static final String CHANNEL_CPU_MAXFREQ = "cpu#maxfreq"; + + /** + * Frequency of the CPU + */ + public static final String CHANNEL_CPU_FREQ = "cpu#freq"; + + /** + * Average recent CPU load + */ + public static final String CHANNEL_CPU_LOAD = "cpu#load"; + + /** + * Average CPU load for the last minute + */ + public static final String CHANNEL_CPU_LOAD_1 = "cpu#load1"; + + /** + * Average CPU load for the last 5 minutes + */ + public static final String CHANNEL_CPU_LOAD_5 = "cpu#load5"; + + /** + * Average CPU load for the last 15 minutes + */ + public static final String CHANNEL_CPU_LOAD_15 = "cpu#load15"; + + /** + * CPU name + */ + public static final String CHANNEL_CPU_NAME = "cpu#name"; + + /** + * CPU uptime in minutes + */ + public static final String CHANNEL_CPU_UPTIME = "cpu#uptime"; + + /** + * CPU running threads count + */ + 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 + */ + public static final String CHANNEL_NETWORK_IP = "network#ip"; + + /** + * Network display name + */ + public static final String CHANNEL_NETWORK_ADAPTER_NAME = "network#networkName"; + + /** + * Network data sent + */ + public static final String CHANNEL_NETWORK_DATA_SENT = "network#dataSent"; + + /** + * Network data received + */ + public static final String CHANNEL_NETWORK_DATA_RECEIVED = "network#dataReceived"; + + /** + * Network packets sent + */ + public static final String CHANNEL_NETWORK_PACKETS_SENT = "network#packetsSent"; + + /** + * Network packets received + */ + public static final String CHANNEL_NETWORK_PACKETS_RECEIVED = "network#packetsReceived"; + + /** + * Network name + */ + public static final String CHANNEL_NETWORK_NAME = "network#networkDisplayName"; + + /** + * Network mac address + */ + 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 + */ + public static final String CHANNEL_GROUP_PROCESS = "process"; + + /** + * CPU load used from a process + */ + + public static final String CHANNEL_PROCESS_LOAD = "process#load"; + + /** + * Size of memory used from a process in MB + */ + public static final String CHANNEL_PROCESS_MEMORY = "process#used"; + + /** + * Name of the process + */ + public static final String CHANNEL_PROCESS_NAME = "process#name"; + + /** + * Number of threads, used form the process + */ + public static final String CHANNEL_PROCESS_THREADS = "process#threads"; + + /** + * The full path of the process + */ + public static final String CHANNEL_PROCESS_PATH = "process#path"; + + // Thing configuraion + /** + * Name of the configuration parameter of the thing that defines refresh time for High priority channels + */ + public static final String HIGH_PRIORITY_REFRESH_TIME = "interval_high"; + + /** + * Name of the configuration parameter of the thing that defines refresh time for Medium priority channels + */ + public static final String MEDIUM_PRIORITY_REFRESH_TIME = "interval_medium"; + + // Channel configuration + + /** + * Name of the channel configuration parameter priority + */ + public static final String PRIOIRITY_PARAM = "priority"; + + /** + * Name of the channel configuration parameter pid + * + */ + public static final String PID_PARAM = "pid"; +} diff --git a/bundles/org.openhab.binding.systeminfo/src/main/java/org/openhab/binding/systeminfo/internal/SystemInfoHandlerFactory.java b/bundles/org.openhab.binding.systeminfo/src/main/java/org/openhab/binding/systeminfo/internal/SystemInfoHandlerFactory.java new file mode 100644 index 0000000000..0b4db4c74c --- /dev/null +++ b/bundles/org.openhab.binding.systeminfo/src/main/java/org/openhab/binding/systeminfo/internal/SystemInfoHandlerFactory.java @@ -0,0 +1,83 @@ +/** + * Copyright (c) 2010-2024 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 org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.systeminfo.internal.handler.SystemInfoHandler; +import org.openhab.binding.systeminfo.internal.model.SystemInfoInterface; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingTypeUID; +import org.openhab.core.thing.binding.BaseThingHandlerFactory; +import org.openhab.core.thing.binding.ThingHandler; +import org.openhab.core.thing.binding.ThingHandlerFactory; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; + +/** + * The {@link SystemInfoHandlerFactory} is responsible for creating things and thing + * handlers. + * + * @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 @NonNullByDefault({}) SystemInfoInterface systeminfo; + private @NonNullByDefault({}) SystemInfoThingTypeProvider thingTypeProvider; + + @Override + public boolean supportsThingType(ThingTypeUID 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 (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; + } + + @Reference + public void bindSystemInfo(SystemInfoInterface systeminfo) { + this.systeminfo = systeminfo; + } + + 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; + } +} diff --git a/bundles/org.openhab.binding.systeminfo/src/main/java/org/openhab/binding/systeminfo/internal/SystemInfoThingTypeProvider.java b/bundles/org.openhab.binding.systeminfo/src/main/java/org/openhab/binding/systeminfo/internal/SystemInfoThingTypeProvider.java new file mode 100644 index 0000000000..93da1c1480 --- /dev/null +++ b/bundles/org.openhab.binding.systeminfo/src/main/java/org/openhab/binding/systeminfo/internal/SystemInfoThingTypeProvider.java @@ -0,0 +1,272 @@ +/** + * Copyright (c) 2010-2024 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.Collections; +import java.util.HashMap; +import java.util.List; +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.storage.StorageService; +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.AbstractStorageBasedTypeProvider; +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 org.openhab.binding.systeminfo.internal.handler.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 extends AbstractStorageBasedTypeProvider { + private final Logger logger = LoggerFactory.getLogger(SystemInfoThingTypeProvider.class); + + private final ThingTypeRegistry thingTypeRegistry; + private final ChannelGroupTypeRegistry channelGroupTypeRegistry; + private final ChannelTypeRegistry channelTypeRegistry; + + private final Map> thingChannelsConfig = new HashMap<>(); + + @Activate + public SystemInfoThingTypeProvider(@Reference ThingTypeRegistry thingTypeRegistry, + @Reference ChannelGroupTypeRegistry channelGroupTypeRegistry, + @Reference ChannelTypeRegistry channelTypeRegistry, @Reference StorageService storageService) { + super(storageService); + this.thingTypeRegistry = thingTypeRegistry; + this.channelGroupTypeRegistry = channelGroupTypeRegistry; + this.channelTypeRegistry = channelTypeRegistry; + } + + /** + * 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 groupDefs) { + ThingType baseType = thingTypeRegistry.getThingType(typeUID); + if (baseType == null) { + baseType = thingTypeRegistry.getThingType(THING_TYPE_COMPUTER); + if (baseType == null) { + logger.warn("Could not find base thing type in registry."); + return false; + } + } + ThingTypeBuilder builder = createThingTypeBuilder(typeUID, baseType.getUID()); + if (builder != null) { + logger.trace("Adding channel group definitions to thing type"); + ThingType type = builder.withChannelGroupDefinitions(groupDefs).build(); + + putThingType(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 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); + builder.withDefaultTags(channelType.getTags()); + String description = channelType.getDescription(); + if (description != null) { + builder.withDescription(description); + } + String itemType = channelType.getItemType(); + if (itemType != null) { + builder.withAcceptedItemType(itemType); + } + return builder.build(); + } + + /** + * 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 org.openhab.core.thing.binding.BaseThingHandler#changeThingType()} method reverts channel configurations + * to their defaults. + * + * @param thing + */ + public void storeChannelsConfig(Thing thing) { + Map 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 restoreChannelsConfig(ThingUID UID) { + Map configs = thingChannelsConfig.remove(UID); + return configs != null ? configs : Collections.emptyMap(); + } +} diff --git a/bundles/org.openhab.binding.systeminfo/src/main/java/org/openhab/binding/systeminfo/internal/SysteminfoBindingConstants.java b/bundles/org.openhab.binding.systeminfo/src/main/java/org/openhab/binding/systeminfo/internal/SysteminfoBindingConstants.java deleted file mode 100644 index ebe6c7ac83..0000000000 --- a/bundles/org.openhab.binding.systeminfo/src/main/java/org/openhab/binding/systeminfo/internal/SysteminfoBindingConstants.java +++ /dev/null @@ -1,487 +0,0 @@ -/** - * Copyright (c) 2010-2024 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 org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.thing.ThingTypeUID; - -/** - * The {@link SysteminfoBindingConstants} class defines common constants, which are - * used across the whole binding. - * - * @author Svilen Valkanov - Initial contribution - * @author Mark Herwege - Add dynamic creation of extra channels - * @author Mark Herwege - Processor frequency channels - */ -@NonNullByDefault -public class SysteminfoBindingConstants { - - public static final String BINDING_ID = "systeminfo"; - - 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 - /** - * Number of CPU logical cores - */ - public static final String PROPERTY_CPU_LOGICAL_CORES = "CPU Logical Cores"; - - /** - * Number of CPU physical cores - */ - public static final String PROPERTY_CPU_PHYSICAL_CORES = "CPU Physical Cores"; - - /** - * Contains information about the family /Windows, Linux, OS X etc/ of the operation system - */ - public static final String PROPERTY_OS_FAMILY = "OS Family"; - - /** - * Name of the manufacturer of the operation system - */ - public static final String PROPERTY_OS_MANUFACTURER = "OS Manufacturer"; - - /** - * Version of the operation system - */ - public static final String PROPERTY_OS_VERSION = "OS Version"; - - // 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 - */ - public static final String CHANNEL_MEMORY_AVAILABLE = "memory#available"; - - /** - * Size of the used memory - */ - public static final String CHANNEL_MEMORY_USED = "memory#used"; - - /** - * Total size of the memory - */ - public static final String CHANNEL_MEMORY_TOTAL = "memory#total"; - - /** - * Percents of the available memory - */ - public static final String CHANNEL_MEMORY_AVAILABLE_PERCENT = "memory#availablePercent"; - - /** - * Percents of the used memory - */ - public static final String CHANNEL_MEMORY_USED_PERCENT = "memory#usedPercent"; - - /** - * Percents of the used heap - */ - public static final String CHANNEL_MEMORY_USED_HEAP_PERCENT = "memory#usedHeapPercent"; - - /** - * Bytes used in the heap - */ - 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 - */ - public static final String CHANNEL_SWAP_TOTAL = "swap#total"; - - /** - * Size of the available swap memory - */ - public static final String CHANNEL_SWAP_AVAILABLE = "swap#available"; - - /** - * Size of the used swap memory - */ - public static final String CHANNEL_SWAP_USED = "swap#used"; - - /** - * Percents of the available swap memory - */ - public static final String CHANNEL_SWAP_AVAILABLE_PERCENT = "swap#availablePercent"; - - /** - * Percents of the used swap memory - */ - 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 - */ - public static final String CHANNEL_DRIVE_NAME = "drive#name"; - - /** - * Physical storage drive model - */ - public static final String CHANNEL_DRIVE_MODEL = "drive#model"; - - /** - * Physical storage drive serial number - */ - 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 - */ - public static final String CHANNEL_STORAGE_NAME = "storage#name"; - - /** - * Logical storage volume type -(e.g. NTFS, FAT32 ..) - */ - public static final String CHANNEL_STORAGE_TYPE = "storage#type"; - - /** - * Description of the logical volume storage - */ - public static final String CHANNEL_STORAGE_DESCRIPTION = "storage#description"; - - /** - * Size of the available storage space - */ - public static final String CHANNEL_STORAGE_AVAILABLE = "storage#available"; - - /** - * Size of the used storage space - */ - public static final String CHANNEL_STORAGE_USED = "storage#used"; - - /** - * Total storage space - */ - public static final String CHANNEL_STORAGE_TOTAL = "storage#total"; - - /** - * Percents of the available storage space - */ - public static final String CHANNEL_STORAGE_AVAILABLE_PERCENT = "storage#availablePercent"; - - /** - * Percents of the used storage space - */ - 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. - */ - public static final String CHANNEL_SENSORS_CPU_TEMPERATURE = "sensors#cpuTemp"; - - /** - * Voltage of the CPU core. - */ - public static final String CHANNEL_SENOSRS_CPU_VOLTAGE = "sensors#cpuVoltage"; - - /** - * Fan speed - */ - 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 - */ - public static final String CHANNEL_BATTERY_NAME = "battery#name"; - - /** - * Remaining capacity of the battery. - */ - public static final String CHANNEL_BATTERY_REMAINING_CAPACITY = "battery#remainingCapacity"; - - /** - * Estimated remaining time of the battery - */ - 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 - */ - public static final String CHANNEL_CPU_DESCRIPTION = "cpu#description"; - - /** - * Maximum frequency of the CPU - */ - public static final String CHANNEL_CPU_MAXFREQ = "cpu#maxfreq"; - - /** - * Frequency of the CPU - */ - public static final String CHANNEL_CPU_FREQ = "cpu#freq"; - - /** - * Average recent CPU load - */ - public static final String CHANNEL_CPU_LOAD = "cpu#load"; - - /** - * Average CPU load for the last minute - */ - public static final String CHANNEL_CPU_LOAD_1 = "cpu#load1"; - - /** - * Average CPU load for the last 5 minutes - */ - public static final String CHANNEL_CPU_LOAD_5 = "cpu#load5"; - - /** - * Average CPU load for the last 15 minutes - */ - public static final String CHANNEL_CPU_LOAD_15 = "cpu#load15"; - - /** - * CPU name - */ - public static final String CHANNEL_CPU_NAME = "cpu#name"; - - /** - * CPU uptime in minutes - */ - public static final String CHANNEL_CPU_UPTIME = "cpu#uptime"; - - /** - * CPU running threads count - */ - 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 - */ - public static final String CHANNEL_NETWORK_IP = "network#ip"; - - /** - * Network display name - */ - public static final String CHANNEL_NETWORK_ADAPTER_NAME = "network#networkName"; - - /** - * Network data sent - */ - public static final String CHANNEL_NETWORK_DATA_SENT = "network#dataSent"; - - /** - * Network data received - */ - public static final String CHANNEL_NETWORK_DATA_RECEIVED = "network#dataReceived"; - - /** - * Network packets sent - */ - public static final String CHANNEL_NETWORK_PACKETS_SENT = "network#packetsSent"; - - /** - * Network packets received - */ - public static final String CHANNEL_NETWORK_PACKETS_RECEIVED = "network#packetsReceived"; - - /** - * Network name - */ - public static final String CHANNEL_NETWORK_NAME = "network#networkDisplayName"; - - /** - * Network mac address - */ - 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 - */ - public static final String CHANNEL_GROUP_PROCESS = "process"; - - /** - * CPU load used from a process - */ - - public static final String CHANNEL_PROCESS_LOAD = "process#load"; - - /** - * Size of memory used from a process in MB - */ - public static final String CHANNEL_PROCESS_MEMORY = "process#used"; - - /** - * Name of the process - */ - public static final String CHANNEL_PROCESS_NAME = "process#name"; - - /** - * Number of threads, used form the process - */ - public static final String CHANNEL_PROCESS_THREADS = "process#threads"; - - /** - * The full path of the process - */ - public static final String CHANNEL_PROCESS_PATH = "process#path"; - - // Thing configuraion - /** - * Name of the configuration parameter of the thing that defines refresh time for High priority channels - */ - public static final String HIGH_PRIORITY_REFRESH_TIME = "interval_high"; - - /** - * Name of the configuration parameter of the thing that defines refresh time for Medium priority channels - */ - public static final String MEDIUM_PRIORITY_REFRESH_TIME = "interval_medium"; - - // Channel configuration - - /** - * Name of the channel configuration parameter priority - */ - public static final String PRIOIRITY_PARAM = "priority"; - - /** - * Name of the channel configuration parameter pid - * - */ - public static final String PID_PARAM = "pid"; -} diff --git a/bundles/org.openhab.binding.systeminfo/src/main/java/org/openhab/binding/systeminfo/internal/SysteminfoHandlerFactory.java b/bundles/org.openhab.binding.systeminfo/src/main/java/org/openhab/binding/systeminfo/internal/SysteminfoHandlerFactory.java deleted file mode 100644 index ded9260601..0000000000 --- a/bundles/org.openhab.binding.systeminfo/src/main/java/org/openhab/binding/systeminfo/internal/SysteminfoHandlerFactory.java +++ /dev/null @@ -1,83 +0,0 @@ -/** - * Copyright (c) 2010-2024 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 org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.systeminfo.internal.handler.SysteminfoHandler; -import org.openhab.binding.systeminfo.internal.model.SysteminfoInterface; -import org.openhab.core.thing.Thing; -import org.openhab.core.thing.ThingTypeUID; -import org.openhab.core.thing.binding.BaseThingHandlerFactory; -import org.openhab.core.thing.binding.ThingHandler; -import org.openhab.core.thing.binding.ThingHandlerFactory; -import org.osgi.service.component.annotations.Component; -import org.osgi.service.component.annotations.Reference; - -/** - * The {@link SysteminfoHandlerFactory} is responsible for creating things and thing - * handlers. - * - * @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 @NonNullByDefault({}) SysteminfoInterface systeminfo; - private @NonNullByDefault({}) SysteminfoThingTypeProvider thingTypeProvider; - - @Override - public boolean supportsThingType(ThingTypeUID 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 (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; - } - - @Reference - public void bindSystemInfo(SysteminfoInterface systeminfo) { - this.systeminfo = systeminfo; - } - - 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; - } -} diff --git a/bundles/org.openhab.binding.systeminfo/src/main/java/org/openhab/binding/systeminfo/internal/SysteminfoThingTypeProvider.java b/bundles/org.openhab.binding.systeminfo/src/main/java/org/openhab/binding/systeminfo/internal/SysteminfoThingTypeProvider.java deleted file mode 100644 index 20ab103161..0000000000 --- a/bundles/org.openhab.binding.systeminfo/src/main/java/org/openhab/binding/systeminfo/internal/SysteminfoThingTypeProvider.java +++ /dev/null @@ -1,272 +0,0 @@ -/** - * Copyright (c) 2010-2024 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.Collections; -import java.util.HashMap; -import java.util.List; -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.storage.StorageService; -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.AbstractStorageBasedTypeProvider; -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 org.openhab.binding.systeminfo.internal.handler.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 extends AbstractStorageBasedTypeProvider { - private final Logger logger = LoggerFactory.getLogger(SysteminfoThingTypeProvider.class); - - private final ThingTypeRegistry thingTypeRegistry; - private final ChannelGroupTypeRegistry channelGroupTypeRegistry; - private final ChannelTypeRegistry channelTypeRegistry; - - private final Map> thingChannelsConfig = new HashMap<>(); - - @Activate - public SysteminfoThingTypeProvider(@Reference ThingTypeRegistry thingTypeRegistry, - @Reference ChannelGroupTypeRegistry channelGroupTypeRegistry, - @Reference ChannelTypeRegistry channelTypeRegistry, @Reference StorageService storageService) { - super(storageService); - this.thingTypeRegistry = thingTypeRegistry; - this.channelGroupTypeRegistry = channelGroupTypeRegistry; - this.channelTypeRegistry = channelTypeRegistry; - } - - /** - * 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 groupDefs) { - ThingType baseType = thingTypeRegistry.getThingType(typeUID); - if (baseType == null) { - baseType = thingTypeRegistry.getThingType(THING_TYPE_COMPUTER); - if (baseType == null) { - logger.warn("Could not find base thing type in registry."); - return false; - } - } - ThingTypeBuilder builder = createThingTypeBuilder(typeUID, baseType.getUID()); - if (builder != null) { - logger.trace("Adding channel group definitions to thing type"); - ThingType type = builder.withChannelGroupDefinitions(groupDefs).build(); - - putThingType(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 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); - builder.withDefaultTags(channelType.getTags()); - String description = channelType.getDescription(); - if (description != null) { - builder.withDescription(description); - } - String itemType = channelType.getItemType(); - if (itemType != null) { - builder.withAcceptedItemType(itemType); - } - return builder.build(); - } - - /** - * 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 org.openhab.core.thing.binding.BaseThingHandler#changeThingType()} method reverts channel configurations - * to their defaults. - * - * @param thing - */ - public void storeChannelsConfig(Thing thing) { - Map 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 restoreChannelsConfig(ThingUID UID) { - Map configs = thingChannelsConfig.remove(UID); - return configs != null ? configs : Collections.emptyMap(); - } -} diff --git a/bundles/org.openhab.binding.systeminfo/src/main/java/org/openhab/binding/systeminfo/internal/discovery/SystemInfoDiscoveryService.java b/bundles/org.openhab.binding.systeminfo/src/main/java/org/openhab/binding/systeminfo/internal/discovery/SystemInfoDiscoveryService.java new file mode 100644 index 0000000000..93a73f40e4 --- /dev/null +++ b/bundles/org.openhab.binding.systeminfo/src/main/java/org/openhab/binding/systeminfo/internal/discovery/SystemInfoDiscoveryService.java @@ -0,0 +1,84 @@ +/** + * Copyright (c) 2010-2024 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.discovery; + +import static org.openhab.binding.systeminfo.internal.SystemInfoBindingConstants.THING_TYPE_COMPUTER; + +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.Set; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.core.config.discovery.AbstractDiscoveryService; +import org.openhab.core.config.discovery.DiscoveryResult; +import org.openhab.core.config.discovery.DiscoveryResultBuilder; +import org.openhab.core.config.discovery.DiscoveryService; +import org.openhab.core.thing.ThingTypeUID; +import org.openhab.core.thing.ThingUID; +import org.osgi.service.component.annotations.Component; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Discovery service implementation for the SystemInfo binding. It creates {@link DiscoveryResult} with + * {@link #DEFAULT_THING_LABEL}. The discovered Thing will have id - the hostname or {@link #DEFAULT_THING_ID}' + * + * @author Svilen Valkanov - Initial contribution + * @author Wouter Born - Add null annotations + */ +@NonNullByDefault +@Component(service = DiscoveryService.class, configurationPid = "discovery.systeminfo") +public class SystemInfoDiscoveryService extends AbstractDiscoveryService { + public static final String DEFAULT_THING_ID = "unknown"; + public static final String DEFAULT_THING_LABEL = "Local computer"; + + private final Logger logger = LoggerFactory.getLogger(SystemInfoDiscoveryService.class); + + private static final Set SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_COMPUTER); + + private static final int DISCOVERY_TIME_SECONDS = 30; + private static final String THING_UID_VALID_CHARS = "A-Za-z0-9_-"; + private static final String HOST_NAME_SEPERATOR = "_"; + + public SystemInfoDiscoveryService() { + super(SUPPORTED_THING_TYPES_UIDS, DISCOVERY_TIME_SECONDS); + } + + @Override + protected void startScan() { + logger.debug("Starting system information discovery !"); + String hostname; + + try { + hostname = getHostName(); + if (hostname.isEmpty()) { + throw new UnknownHostException(); + } + if (!hostname.matches("[" + THING_UID_VALID_CHARS + "]*")) { + hostname = hostname.replaceAll("[^" + THING_UID_VALID_CHARS + "]", HOST_NAME_SEPERATOR); + } + } catch (UnknownHostException ex) { + hostname = DEFAULT_THING_ID; + logger.info("Hostname can not be resolved. Computer name will be set to the default one: {}", + DEFAULT_THING_ID); + } + + ThingUID computer = new ThingUID(THING_TYPE_COMPUTER, hostname); + thingDiscovered(DiscoveryResultBuilder.create(computer).withLabel(DEFAULT_THING_LABEL).build()); + } + + protected String getHostName() throws UnknownHostException { + InetAddress addr = InetAddress.getLocalHost(); + return addr.getHostName(); + } +} diff --git a/bundles/org.openhab.binding.systeminfo/src/main/java/org/openhab/binding/systeminfo/internal/discovery/SysteminfoDiscoveryService.java b/bundles/org.openhab.binding.systeminfo/src/main/java/org/openhab/binding/systeminfo/internal/discovery/SysteminfoDiscoveryService.java deleted file mode 100644 index b2c90cfad9..0000000000 --- a/bundles/org.openhab.binding.systeminfo/src/main/java/org/openhab/binding/systeminfo/internal/discovery/SysteminfoDiscoveryService.java +++ /dev/null @@ -1,86 +0,0 @@ -/** - * Copyright (c) 2010-2024 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.discovery; - -import static org.openhab.binding.systeminfo.internal.SysteminfoBindingConstants.THING_TYPE_COMPUTER; - -import java.net.InetAddress; -import java.net.UnknownHostException; -import java.util.Set; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.systeminfo.internal.SysteminfoBindingConstants; -import org.openhab.core.config.discovery.AbstractDiscoveryService; -import org.openhab.core.config.discovery.DiscoveryResult; -import org.openhab.core.config.discovery.DiscoveryResultBuilder; -import org.openhab.core.config.discovery.DiscoveryService; -import org.openhab.core.thing.ThingTypeUID; -import org.openhab.core.thing.ThingUID; -import org.osgi.service.component.annotations.Component; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Discovery service implementation for the Systeminfo binding. It creates {@link DiscoveryResult} with - * {@link #DEFAULT_THING_LABEL}. The discovered Thing will have id - the hostname or {@link #DEFAULT_THING_ID}' - * - * @author Svilen Valkanov - Initial contribution - * @author Wouter Born - Add null annotations - */ -@NonNullByDefault -@Component(service = DiscoveryService.class, configurationPid = "discovery.systeminfo") -public class SysteminfoDiscoveryService extends AbstractDiscoveryService { - public static final String DEFAULT_THING_ID = "unknown"; - public static final String DEFAULT_THING_LABEL = "Local computer"; - - private final Logger logger = LoggerFactory.getLogger(SysteminfoDiscoveryService.class); - - private static final Set SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_COMPUTER); - - private static final int DISCOVERY_TIME_SECONDS = 30; - private static final String THING_UID_VALID_CHARS = "A-Za-z0-9_-"; - private static final String HOST_NAME_SEPERATOR = "_"; - - public SysteminfoDiscoveryService() { - super(SUPPORTED_THING_TYPES_UIDS, DISCOVERY_TIME_SECONDS); - } - - @Override - protected void startScan() { - logger.debug("Starting system information discovery !"); - String hostname; - - try { - hostname = getHostName(); - if (hostname.isEmpty()) { - throw new UnknownHostException(); - } - if (!hostname.matches("[" + THING_UID_VALID_CHARS + "]*")) { - hostname = hostname.replaceAll("[^" + THING_UID_VALID_CHARS + "]", HOST_NAME_SEPERATOR); - } - } catch (UnknownHostException ex) { - hostname = DEFAULT_THING_ID; - logger.info("Hostname can not be resolved. Computer name will be set to the default one: {}", - DEFAULT_THING_ID); - } - - ThingTypeUID computerType = SysteminfoBindingConstants.THING_TYPE_COMPUTER; - ThingUID computer = new ThingUID(computerType, hostname); - thingDiscovered(DiscoveryResultBuilder.create(computer).withLabel(DEFAULT_THING_LABEL).build()); - } - - protected String getHostName() throws UnknownHostException { - InetAddress addr = InetAddress.getLocalHost(); - return addr.getHostName(); - } -} diff --git a/bundles/org.openhab.binding.systeminfo/src/main/java/org/openhab/binding/systeminfo/internal/handler/SystemInfoHandler.java b/bundles/org.openhab.binding.systeminfo/src/main/java/org/openhab/binding/systeminfo/internal/handler/SystemInfoHandler.java new file mode 100644 index 0000000000..366ee9405c --- /dev/null +++ b/bundles/org.openhab.binding.systeminfo/src/main/java/org/openhab/binding/systeminfo/internal/handler/SystemInfoHandler.java @@ -0,0 +1,829 @@ +/** + * Copyright (c) 2010-2024 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.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.List; +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; +import org.openhab.core.thing.Channel; +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; +import org.openhab.core.types.UnDefType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link SystemInfoHandler} is responsible for providing real time information about the system + * (CPU, Memory, Storage, Display and others). + * + * @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 + * @author Mark Herwege - Processor frequency channels + */ +@NonNullByDefault +public class SystemInfoHandler extends BaseThingHandler { + /** + * Refresh interval for {@link #highPriorityChannels} in seconds. + */ + private @NonNullByDefault({}) BigDecimal refreshIntervalHighPriority; + + /** + * Refresh interval for {@link #mediumPriorityChannels} in seconds. + */ + private @NonNullByDefault({}) BigDecimal refreshIntervalMediumPriority; + + /** + * Channels with priority configuration parameter set to High. They usually need frequent update of the state like + * CPU load, or information about the free and used memory. + * They are updated periodically at {@link #refreshIntervalHighPriority}. + */ + private final Set highPriorityChannels = new HashSet<>(); + + /** + * Channels with priority configuration parameter set to Medium. These channels usually need update of the + * state not so oft like battery capacity, storage used and etc. + * They are updated periodically at {@link #refreshIntervalMediumPriority}. + */ + private final Set mediumPriorityChannels = new HashSet<>(); + + /** + * Channels with priority configuration parameter set to Low. They represent static information or information + * that is updated rare- e.g. CPU name, storage name and etc. + * They are updated only at {@link #initialize()}. + */ + private final Set lowPriorityChannels = new HashSet<>(); + + /** + * Wait time for the creation of Item-Channel links in seconds. This delay is needed, because the Item-Channel + * links have to be created before the thing state is updated, otherwise item state will not be updated. + */ + 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; + + /** + * 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 cpuLoadCache = new ExpiringCache<>(MIN_PROCESS_LOAD_REFRESH_INTERVAL_MS, + () -> getSystemCpuLoad()); + private ExpiringCacheMap processLoadCache = new ExpiringCacheMap<>( + MIN_PROCESS_LOAD_REFRESH_INTERVAL_MS); + + private final Logger logger = LoggerFactory.getLogger(SystemInfoHandler.class); + + public SystemInfoHandler(Thing thing, SystemInfoThingTypeProvider thingTypeProvider, + SystemInfoInterface systeminfo) { + super(thing); + 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()) { + 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, + "@text/offline.cannot-initialize"); + } + } + + @Override + public void handleRemoval() { + thingTypeProvider.removeThingType(thing.getThingTypeUID()); + super.handleRemoval(); + } + + private boolean instantiateSystemInfoLibrary() { + try { + systeminfo.initializeSystemInfo(); + logger.debug("SystemInfo implementation is instantiated!"); + return true; + } catch (Exception e) { + logger.warn("Cannot instantiate SystemInfo object!", e); + return false; + } + } + + private boolean isConfigurationValid() { + logger.debug("Start reading Thing configuration."); + try { + refreshIntervalMediumPriority = (BigDecimal) this.thing.getConfiguration() + .get(MEDIUM_PRIORITY_REFRESH_TIME); + refreshIntervalHighPriority = (BigDecimal) this.thing.getConfiguration().get(HIGH_PRIORITY_REFRESH_TIME); + + if (refreshIntervalHighPriority.intValue() <= 0 || refreshIntervalMediumPriority.intValue() <= 0) { + throw new IllegalArgumentException("Refresh time must be positive number!"); + } + logger.debug("Refresh time for medium priority channels set to {} s", refreshIntervalMediumPriority); + logger.debug("Refresh time for high priority channels set to {} s", refreshIntervalHighPriority); + return true; + } catch (IllegalArgumentException e) { + logger.warn("Refresh time value is invalid! Please change the thing configuration!"); + return false; + } catch (ClassCastException e) { + logger.debug("Channel configuration cannot be read!"); + return false; + } + } + + private boolean updateProperties() { + Map properties = editProperties(); + try { + properties.put(PROPERTY_CPU_LOGICAL_CORES, systeminfo.getCpuLogicalCores().toString()); + properties.put(PROPERTY_CPU_PHYSICAL_CORES, systeminfo.getCpuPhysicalCores().toString()); + properties.put(PROPERTY_OS_FAMILY, systeminfo.getOsFamily().toString()); + properties.put(PROPERTY_OS_MANUFACTURER, systeminfo.getOsManufacturer().toString()); + properties.put(PROPERTY_OS_VERSION, systeminfo.getOsVersion().toString()); + updateProperties(properties); + logger.debug("Properties updated!"); + return true; + } catch (Exception e) { + logger.debug("Cannot get system properties! Please try to restart the binding.", e); + return false; + } + } + + /** + * 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. + *

+ * 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 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 newChannels = new ArrayList<>(); + newChannels.addAll(createChannels(thingUID, CHANNEL_SENSORS_FAN_SPEED, systeminfo.getFanCount())); + newChannels.addAll(createChannels(thingUID, CHANNEL_CPU_FREQ, systeminfo.getCpuLogicalCores().intValue())); + if (!newChannels.isEmpty()) { + logger.debug("Creating additional channels"); + newChannels.addAll(0, thing.getChannels()); + ThingBuilder thingBuilder = editThing(); + thingBuilder.withChannels(newChannels); + updateThing(thingBuilder.build()); + } + + return false; + } + + private List createChannelGroups(ThingUID thingUID, String channelGroupID, + String channelGroupTypeID, int count) { + if (count <= 1) { + return Collections.emptyList(); + } + + List channelGroups = thingTypeProvider.getChannelGroupDefinitions(thing.getThingTypeUID()).stream() + .map(ChannelGroupDefinition::getId).collect(Collectors.toList()); + + List 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 createChannels(ThingUID thingUID, String channelID, int count) { + if (count <= 1) { + return Collections.emptyList(); + } + + List 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 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"); + List 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!", + channel.getUID()); + break; + } + switch (priority) { + case "High": + highPriorityChannels.add(channel.getUID()); + break; + case "Medium": + mediumPriorityChannels.add(channel.getUID()); + break; + case "Low": + lowPriorityChannels.add(channel.getUID()); + break; + default: + logger.debug("Invalid priority configuration parameter. Channel will not be updated!"); + } + } + } + + private void changeChannelPriority(ChannelUID channelUID, String priority) { + switch (priority) { + case "High": + mediumPriorityChannels.remove(channelUID); + lowPriorityChannels.remove(channelUID); + highPriorityChannels.add(channelUID); + break; + case "Medium": + lowPriorityChannels.remove(channelUID); + highPriorityChannels.remove(channelUID); + mediumPriorityChannels.add(channelUID); + break; + case "Low": + highPriorityChannels.remove(channelUID); + mediumPriorityChannels.remove(channelUID); + lowPriorityChannels.add(channelUID); + break; + default: + logger.debug("Invalid priority configuration parameter. Channel will not be updated!"); + } + } + + private void scheduleUpdates() { + 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); + 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"); + scheduler.schedule(() -> { + publishData(lowPriorityChannels); + }, WAIT_TIME_CHANNEL_ITEM_LINK_INIT, TimeUnit.SECONDS); + } + + private void publishData(Set channels) { + // if handler disposed while waiting for the links, don't update the channel states + if (!ThingStatus.ONLINE.equals(thing.getStatus())) { + return; + } + for (ChannelUID channeUID : channels) { + if (isLinked(channeUID)) { + publishDataForChannel(channeUID); + } + } + } + + private void publishDataForChannel(ChannelUID channelUID) { + State state = getInfoForChannel(channelUID); + String channelID = channelUID.getId(); + updateState(channelID, state); + } + + public Set getHighPriorityChannels() { + return highPriorityChannels; + } + + public Set getMediumPriorityChannels() { + return mediumPriorityChannels; + } + + public Set getLowPriorityChannels() { + return lowPriorityChannels; + } + + /** + * This method gets the information for specific channel through the {@link SystemInfoInterface}. It uses the + * channel ID to call the correct method from the {@link SystemInfoInterface} with deviceIndex parameter (in case of + * multiple devices, for reference see {@link SystemInfoHandler#getDeviceIndex(ChannelUID)}}) + * + * @param channelUID the UID of the channel + * @return State object or null, if there is no information for the device with this index + */ + private State getInfoForChannel(ChannelUID channelUID) { + State state = null; + + String channelID = channelUID.getId(); + int deviceIndex = getDeviceIndex(channelUID); + + 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 { + switch (channelID) { + case CHANNEL_MEMORY_HEAP_AVAILABLE: + state = new QuantityType<>(Runtime.getRuntime().freeMemory(), Units.BYTE); + break; + case CHANNEL_MEMORY_USED_HEAP_PERCENT: + state = new QuantityType<>((Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()) + * 100 / Runtime.getRuntime().maxMemory(), Units.PERCENT); + break; + case CHANNEL_DISPLAY_INFORMATION: + state = systeminfo.getDisplayInformation(deviceIndex); + break; + case CHANNEL_BATTERY_NAME: + state = systeminfo.getBatteryName(deviceIndex); + break; + case CHANNEL_BATTERY_REMAINING_CAPACITY: + state = new QuantityType<>(systeminfo.getBatteryRemainingCapacity(deviceIndex), Units.PERCENT); + break; + case CHANNEL_BATTERY_REMAINING_TIME: + state = systeminfo.getBatteryRemainingTime(deviceIndex); + break; + case CHANNEL_SENSORS_CPU_TEMPERATURE: + state = systeminfo.getSensorsCpuTemperature(); + break; + case CHANNEL_SENOSRS_CPU_VOLTAGE: + state = systeminfo.getSensorsCpuVoltage(); + break; + case CHANNEL_SENSORS_FAN_SPEED: + state = systeminfo.getSensorsFanSpeed(deviceIndex); + break; + case CHANNEL_CPU_MAXFREQ: + state = systeminfo.getCpuMaxFreq(); + break; + case CHANNEL_CPU_FREQ: + state = systeminfo.getCpuFreq(deviceIndex); + break; + case CHANNEL_CPU_LOAD: + PercentType cpuLoad = cpuLoadCache.getValue(); + state = (cpuLoad != null) ? new QuantityType<>(cpuLoad, Units.PERCENT) : null; + break; + case CHANNEL_CPU_LOAD_1: + state = systeminfo.getCpuLoad1(); + break; + case CHANNEL_CPU_LOAD_5: + state = systeminfo.getCpuLoad5(); + break; + case CHANNEL_CPU_LOAD_15: + state = systeminfo.getCpuLoad15(); + break; + case CHANNEL_CPU_UPTIME: + state = systeminfo.getCpuUptime(); + break; + case CHANNEL_CPU_THREADS: + state = systeminfo.getCpuThreads(); + break; + case CHANNEL_CPU_DESCRIPTION: + state = systeminfo.getCpuDescription(); + break; + case CHANNEL_CPU_NAME: + state = systeminfo.getCpuName(); + break; + case CHANNEL_MEMORY_AVAILABLE: + state = systeminfo.getMemoryAvailable(); + break; + case CHANNEL_MEMORY_USED: + state = systeminfo.getMemoryUsed(); + break; + case CHANNEL_MEMORY_TOTAL: + state = systeminfo.getMemoryTotal(); + break; + case CHANNEL_MEMORY_AVAILABLE_PERCENT: + PercentType memoryAvailablePercent = systeminfo.getMemoryAvailablePercent(); + state = (memoryAvailablePercent != null) ? new QuantityType<>(memoryAvailablePercent, Units.PERCENT) + : null; + break; + case CHANNEL_MEMORY_USED_PERCENT: + PercentType memoryUsedPercent = systeminfo.getMemoryUsedPercent(); + state = (memoryUsedPercent != null) ? new QuantityType<>(memoryUsedPercent, Units.PERCENT) : null; + break; + case CHANNEL_SWAP_AVAILABLE: + state = systeminfo.getSwapAvailable(); + break; + case CHANNEL_SWAP_USED: + state = systeminfo.getSwapUsed(); + break; + case CHANNEL_SWAP_TOTAL: + state = systeminfo.getSwapTotal(); + break; + case CHANNEL_SWAP_AVAILABLE_PERCENT: + PercentType swapAvailablePercent = systeminfo.getSwapAvailablePercent(); + state = (swapAvailablePercent != null) ? new QuantityType<>(swapAvailablePercent, Units.PERCENT) + : null; + break; + case CHANNEL_SWAP_USED_PERCENT: + PercentType swapUsedPercent = systeminfo.getSwapUsedPercent(); + state = (swapUsedPercent != null) ? new QuantityType<>(swapUsedPercent, Units.PERCENT) : null; + break; + case CHANNEL_DRIVE_MODEL: + state = systeminfo.getDriveModel(deviceIndex); + break; + case CHANNEL_DRIVE_SERIAL: + state = systeminfo.getDriveSerialNumber(deviceIndex); + break; + case CHANNEL_DRIVE_NAME: + state = systeminfo.getDriveName(deviceIndex); + break; + case CHANNEL_STORAGE_NAME: + state = systeminfo.getStorageName(deviceIndex); + break; + case CHANNEL_STORAGE_DESCRIPTION: + state = systeminfo.getStorageDescription(deviceIndex); + break; + case CHANNEL_STORAGE_AVAILABLE: + state = systeminfo.getStorageAvailable(deviceIndex); + break; + case CHANNEL_STORAGE_USED: + state = systeminfo.getStorageUsed(deviceIndex); + break; + case CHANNEL_STORAGE_TOTAL: + state = systeminfo.getStorageTotal(deviceIndex); + break; + case CHANNEL_STORAGE_TYPE: + state = systeminfo.getStorageType(deviceIndex); + break; + case CHANNEL_STORAGE_AVAILABLE_PERCENT: + PercentType storageAvailablePercent = systeminfo.getStorageAvailablePercent(deviceIndex); + state = (storageAvailablePercent != null) + ? new QuantityType<>(storageAvailablePercent, Units.PERCENT) + : null; + break; + case CHANNEL_STORAGE_USED_PERCENT: + PercentType storageUsedPercent = systeminfo.getStorageUsedPercent(deviceIndex); + state = (storageUsedPercent != null) ? new QuantityType<>(storageUsedPercent, Units.PERCENT) : null; + break; + case CHANNEL_NETWORK_IP: + state = systeminfo.getNetworkIp(deviceIndex); + break; + case CHANNEL_NETWORK_ADAPTER_NAME: + state = systeminfo.getNetworkDisplayName(deviceIndex); + break; + case CHANNEL_NETWORK_NAME: + state = systeminfo.getNetworkName(deviceIndex); + break; + case CHANNEL_NETWORK_MAC: + state = systeminfo.getNetworkMac(deviceIndex); + break; + case CHANNEL_NETWORK_DATA_SENT: + state = systeminfo.getNetworkDataSent(deviceIndex); + break; + case CHANNEL_NETWORK_DATA_RECEIVED: + state = systeminfo.getNetworkDataReceived(deviceIndex); + break; + case CHANNEL_NETWORK_PACKETS_RECEIVED: + state = systeminfo.getNetworkPacketsReceived(deviceIndex); + break; + case CHANNEL_NETWORK_PACKETS_SENT: + state = systeminfo.getNetworkPacketsSent(deviceIndex); + break; + case CHANNEL_PROCESS_LOAD: + 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); + } catch (Exception e) { + logger.debug("Unexpected error occurred while getting system information!", e); + 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 + * first will have deviceIndex=0, the second deviceIndex=1 ant etc). + * When no device index is specified, default value of 0 (first device in the list) is returned. + * + * @param channelUID the ID of the channel + * @return natural number (number >=0) + */ + private int getDeviceIndex(ChannelUID channelUID) { + String channelID = channelUID.getId(); + String channelGroupID = channelUID.getGroupId(); + if (channelGroupID == null) { + return 0; + } + + if (channelGroupID.contains(CHANNEL_GROUP_PROCESS)) { + // Only in this case the deviceIndex is part of the channel configuration - PID (Process Identifier) + int pid = getPID(channelUID); + logger.debug("Channel with UID {} tracks process with PID: {}", channelUID, pid); + return pid; + } + + if (channelGroupID.contains(CHANNEL_GROUP_CURRENT_PROCESS)) { + return systeminfo.getCurrentProcessID(); + } + + // 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; + } + + /** + * This method gets the process identifier (PID) for specific process + * + * @param channelUID channel unique identifier + * @return natural number + */ + private int getPID(ChannelUID channelUID) { + int pid = 0; + try { + Channel channel = this.thing.getChannel(channelUID.getId()); + if (channel != null) { + Configuration channelProperties = channel.getConfiguration(); + BigDecimal pidValue = (BigDecimal) channelProperties.get(PID_PARAM); + if (pidValue == null || pidValue.intValue() < 0) { + throw new IllegalArgumentException("Invalid value for Process Identifier."); + } else { + pid = pidValue.intValue(); + } + } else { + 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); + } catch (IllegalArgumentException e) { + logger.debug("PID (Process Identifier) must be positive number. Fall back to default value. ", e); + } + return pid; + } + + @Override + 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); + publishDataForChannel(channelUID); + } else { + logger.debug("Unsupported command {} ! Supported commands: REFRESH", command); + } + } else { + logger.debug("Cannot handle command. Thing is not ONLINE."); + } + } + + private boolean isConfigurationKeyChanged(Configuration currentConfig, Configuration newConfig, String key) { + Object currentValue = currentConfig.get(key); + Object newValue = newConfig.get(key); + + if (currentValue == null) { + return (newValue != null); + } + + return !currentValue.equals(newValue); + } + + @Override + public synchronized void thingUpdated(Thing thing) { + logger.trace("About to update thing"); + boolean isChannelConfigChanged = false; + + List channels = thing.getChannels(); + + for (Channel channel : channels) { + ChannelUID channelUID = channel.getUID(); + Configuration newChannelConfig = channel.getConfiguration(); + Channel oldChannel = this.thing.getChannel(channelUID.getId()); + + if (oldChannel == null) { + logger.warn("Channel with UID {} cannot be updated, as it cannot be found!", channelUID); + continue; + } + Configuration currentChannelConfig = oldChannel.getConfiguration(); + + if (isConfigurationKeyChanged(currentChannelConfig, newChannelConfig, PRIOIRITY_PARAM)) { + isChannelConfigChanged = true; + + handleChannelConfigurationChange(oldChannel, newChannelConfig, PRIOIRITY_PARAM); + + String newPriority = (String) newChannelConfig.get(PRIOIRITY_PARAM); + changeChannelPriority(channelUID, newPriority); + } + + if (isConfigurationKeyChanged(currentChannelConfig, newChannelConfig, PID_PARAM)) { + isChannelConfigChanged = true; + handleChannelConfigurationChange(oldChannel, newChannelConfig, PID_PARAM); + } + } + + if (!(isInitialized() && isChannelConfigChanged)) { + super.thingUpdated(thing); + } + } + + private void handleChannelConfigurationChange(Channel channel, Configuration newConfig, String parameter) { + Configuration configuration = channel.getConfiguration(); + Object oldValue = configuration.get(parameter); + + configuration.put(parameter, newConfig.get(parameter)); + + Object newValue = newConfig.get(parameter); + logger.debug("Channel with UID {} has changed its {} from {} to {}", channel.getUID(), parameter, oldValue, + newValue); + 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!"); + localHighPriorityTasks.cancel(true); + } + + ScheduledFuture localMediumPriorityTasks = mediumPriorityTasks; + if (localMediumPriorityTasks != null) { + logger.debug("Medium prioriy tasks will not be run anymore!"); + localMediumPriorityTasks.cancel(true); + } + } + + @Override + public void dispose() { + stopScheduledUpdates(); + } +} diff --git a/bundles/org.openhab.binding.systeminfo/src/main/java/org/openhab/binding/systeminfo/internal/handler/SysteminfoHandler.java b/bundles/org.openhab.binding.systeminfo/src/main/java/org/openhab/binding/systeminfo/internal/handler/SysteminfoHandler.java deleted file mode 100644 index eb6fccc30c..0000000000 --- a/bundles/org.openhab.binding.systeminfo/src/main/java/org/openhab/binding/systeminfo/internal/handler/SysteminfoHandler.java +++ /dev/null @@ -1,829 +0,0 @@ -/** - * Copyright (c) 2010-2024 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.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.List; -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; -import org.openhab.core.thing.Channel; -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; -import org.openhab.core.types.UnDefType; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * The {@link SysteminfoHandler} is responsible for providing real time information about the system - * (CPU, Memory, Storage, Display and others). - * - * @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 - * @author Mark Herwege - Processor frequency channels - */ -@NonNullByDefault -public class SysteminfoHandler extends BaseThingHandler { - /** - * Refresh interval for {@link #highPriorityChannels} in seconds. - */ - private @NonNullByDefault({}) BigDecimal refreshIntervalHighPriority; - - /** - * Refresh interval for {@link #mediumPriorityChannels} in seconds. - */ - private @NonNullByDefault({}) BigDecimal refreshIntervalMediumPriority; - - /** - * Channels with priority configuration parameter set to High. They usually need frequent update of the state like - * CPU load, or information about the free and used memory. - * They are updated periodically at {@link #refreshIntervalHighPriority}. - */ - private final Set highPriorityChannels = new HashSet<>(); - - /** - * Channels with priority configuration parameter set to Medium. These channels usually need update of the - * state not so oft like battery capacity, storage used and etc. - * They are updated periodically at {@link #refreshIntervalMediumPriority}. - */ - private final Set mediumPriorityChannels = new HashSet<>(); - - /** - * Channels with priority configuration parameter set to Low. They represent static information or information - * that is updated rare- e.g. CPU name, storage name and etc. - * They are updated only at {@link #initialize()}. - */ - private final Set lowPriorityChannels = new HashSet<>(); - - /** - * Wait time for the creation of Item-Channel links in seconds. This delay is needed, because the Item-Channel - * links have to be created before the thing state is updated, otherwise item state will not be updated. - */ - 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; - - /** - * 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 cpuLoadCache = new ExpiringCache<>(MIN_PROCESS_LOAD_REFRESH_INTERVAL_MS, - () -> getSystemCpuLoad()); - private ExpiringCacheMap processLoadCache = new ExpiringCacheMap<>( - MIN_PROCESS_LOAD_REFRESH_INTERVAL_MS); - - private final Logger logger = LoggerFactory.getLogger(SysteminfoHandler.class); - - public SysteminfoHandler(Thing thing, SysteminfoThingTypeProvider thingTypeProvider, - SysteminfoInterface systeminfo) { - super(thing); - 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()) { - 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, - "@text/offline.cannot-initialize"); - } - } - - @Override - public void handleRemoval() { - thingTypeProvider.removeThingType(thing.getThingTypeUID()); - super.handleRemoval(); - } - - private boolean instantiateSysteminfoLibrary() { - try { - systeminfo.initializeSysteminfo(); - logger.debug("Systeminfo implementation is instantiated!"); - return true; - } catch (Exception e) { - logger.warn("Cannot instantiate Systeminfo object!", e); - return false; - } - } - - private boolean isConfigurationValid() { - logger.debug("Start reading Thing configuration."); - try { - refreshIntervalMediumPriority = (BigDecimal) this.thing.getConfiguration() - .get(MEDIUM_PRIORITY_REFRESH_TIME); - refreshIntervalHighPriority = (BigDecimal) this.thing.getConfiguration().get(HIGH_PRIORITY_REFRESH_TIME); - - if (refreshIntervalHighPriority.intValue() <= 0 || refreshIntervalMediumPriority.intValue() <= 0) { - throw new IllegalArgumentException("Refresh time must be positive number!"); - } - logger.debug("Refresh time for medium priority channels set to {} s", refreshIntervalMediumPriority); - logger.debug("Refresh time for high priority channels set to {} s", refreshIntervalHighPriority); - return true; - } catch (IllegalArgumentException e) { - logger.warn("Refresh time value is invalid! Please change the thing configuration!"); - return false; - } catch (ClassCastException e) { - logger.debug("Channel configuration cannot be read!"); - return false; - } - } - - private boolean updateProperties() { - Map properties = editProperties(); - try { - properties.put(PROPERTY_CPU_LOGICAL_CORES, systeminfo.getCpuLogicalCores().toString()); - properties.put(PROPERTY_CPU_PHYSICAL_CORES, systeminfo.getCpuPhysicalCores().toString()); - properties.put(PROPERTY_OS_FAMILY, systeminfo.getOsFamily().toString()); - properties.put(PROPERTY_OS_MANUFACTURER, systeminfo.getOsManufacturer().toString()); - properties.put(PROPERTY_OS_VERSION, systeminfo.getOsVersion().toString()); - updateProperties(properties); - logger.debug("Properties updated!"); - return true; - } catch (Exception e) { - logger.debug("Cannot get system properties! Please try to restart the binding.", e); - return false; - } - } - - /** - * 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. - *

- * 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 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 newChannels = new ArrayList<>(); - newChannels.addAll(createChannels(thingUID, CHANNEL_SENSORS_FAN_SPEED, systeminfo.getFanCount())); - newChannels.addAll(createChannels(thingUID, CHANNEL_CPU_FREQ, systeminfo.getCpuLogicalCores().intValue())); - if (!newChannels.isEmpty()) { - logger.debug("Creating additional channels"); - newChannels.addAll(0, thing.getChannels()); - ThingBuilder thingBuilder = editThing(); - thingBuilder.withChannels(newChannels); - updateThing(thingBuilder.build()); - } - - return false; - } - - private List createChannelGroups(ThingUID thingUID, String channelGroupID, - String channelGroupTypeID, int count) { - if (count <= 1) { - return Collections.emptyList(); - } - - List channelGroups = thingTypeProvider.getChannelGroupDefinitions(thing.getThingTypeUID()).stream() - .map(ChannelGroupDefinition::getId).collect(Collectors.toList()); - - List 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 createChannels(ThingUID thingUID, String channelID, int count) { - if (count <= 1) { - return Collections.emptyList(); - } - - List 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 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"); - List 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!", - channel.getUID()); - break; - } - switch (priority) { - case "High": - highPriorityChannels.add(channel.getUID()); - break; - case "Medium": - mediumPriorityChannels.add(channel.getUID()); - break; - case "Low": - lowPriorityChannels.add(channel.getUID()); - break; - default: - logger.debug("Invalid priority configuration parameter. Channel will not be updated!"); - } - } - } - - private void changeChannelPriority(ChannelUID channelUID, String priority) { - switch (priority) { - case "High": - mediumPriorityChannels.remove(channelUID); - lowPriorityChannels.remove(channelUID); - highPriorityChannels.add(channelUID); - break; - case "Medium": - lowPriorityChannels.remove(channelUID); - highPriorityChannels.remove(channelUID); - mediumPriorityChannels.add(channelUID); - break; - case "Low": - highPriorityChannels.remove(channelUID); - mediumPriorityChannels.remove(channelUID); - lowPriorityChannels.add(channelUID); - break; - default: - logger.debug("Invalid priority configuration parameter. Channel will not be updated!"); - } - } - - private void scheduleUpdates() { - 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); - 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"); - scheduler.schedule(() -> { - publishData(lowPriorityChannels); - }, WAIT_TIME_CHANNEL_ITEM_LINK_INIT, TimeUnit.SECONDS); - } - - private void publishData(Set channels) { - // if handler disposed while waiting for the links, don't update the channel states - if (!ThingStatus.ONLINE.equals(thing.getStatus())) { - return; - } - for (ChannelUID channeUID : channels) { - if (isLinked(channeUID)) { - publishDataForChannel(channeUID); - } - } - } - - private void publishDataForChannel(ChannelUID channelUID) { - State state = getInfoForChannel(channelUID); - String channelID = channelUID.getId(); - updateState(channelID, state); - } - - public Set getHighPriorityChannels() { - return highPriorityChannels; - } - - public Set getMediumPriorityChannels() { - return mediumPriorityChannels; - } - - public Set getLowPriorityChannels() { - return lowPriorityChannels; - } - - /** - * This method gets the information for specific channel through the {@link SysteminfoInterface}. It uses the - * channel ID to call the correct method from the {@link SysteminfoInterface} with deviceIndex parameter (in case of - * multiple devices, for reference see {@link #getDeviceIndex(String)}}) - * - * @param channelUID the UID of the channel - * @return State object or null, if there is no information for the device with this index - */ - private State getInfoForChannel(ChannelUID channelUID) { - State state = null; - - String channelID = channelUID.getId(); - int deviceIndex = getDeviceIndex(channelUID); - - 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 { - switch (channelID) { - case CHANNEL_MEMORY_HEAP_AVAILABLE: - state = new QuantityType<>(Runtime.getRuntime().freeMemory(), Units.BYTE); - break; - case CHANNEL_MEMORY_USED_HEAP_PERCENT: - state = new QuantityType<>((Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()) - * 100 / Runtime.getRuntime().maxMemory(), Units.PERCENT); - break; - case CHANNEL_DISPLAY_INFORMATION: - state = systeminfo.getDisplayInformation(deviceIndex); - break; - case CHANNEL_BATTERY_NAME: - state = systeminfo.getBatteryName(deviceIndex); - break; - case CHANNEL_BATTERY_REMAINING_CAPACITY: - state = new QuantityType<>(systeminfo.getBatteryRemainingCapacity(deviceIndex), Units.PERCENT); - break; - case CHANNEL_BATTERY_REMAINING_TIME: - state = systeminfo.getBatteryRemainingTime(deviceIndex); - break; - case CHANNEL_SENSORS_CPU_TEMPERATURE: - state = systeminfo.getSensorsCpuTemperature(); - break; - case CHANNEL_SENOSRS_CPU_VOLTAGE: - state = systeminfo.getSensorsCpuVoltage(); - break; - case CHANNEL_SENSORS_FAN_SPEED: - state = systeminfo.getSensorsFanSpeed(deviceIndex); - break; - case CHANNEL_CPU_MAXFREQ: - state = systeminfo.getCpuMaxFreq(); - break; - case CHANNEL_CPU_FREQ: - state = systeminfo.getCpuFreq(deviceIndex); - break; - case CHANNEL_CPU_LOAD: - PercentType cpuLoad = cpuLoadCache.getValue(); - state = (cpuLoad != null) ? new QuantityType<>(cpuLoad, Units.PERCENT) : null; - break; - case CHANNEL_CPU_LOAD_1: - state = systeminfo.getCpuLoad1(); - break; - case CHANNEL_CPU_LOAD_5: - state = systeminfo.getCpuLoad5(); - break; - case CHANNEL_CPU_LOAD_15: - state = systeminfo.getCpuLoad15(); - break; - case CHANNEL_CPU_UPTIME: - state = systeminfo.getCpuUptime(); - break; - case CHANNEL_CPU_THREADS: - state = systeminfo.getCpuThreads(); - break; - case CHANNEL_CPU_DESCRIPTION: - state = systeminfo.getCpuDescription(); - break; - case CHANNEL_CPU_NAME: - state = systeminfo.getCpuName(); - break; - case CHANNEL_MEMORY_AVAILABLE: - state = systeminfo.getMemoryAvailable(); - break; - case CHANNEL_MEMORY_USED: - state = systeminfo.getMemoryUsed(); - break; - case CHANNEL_MEMORY_TOTAL: - state = systeminfo.getMemoryTotal(); - break; - case CHANNEL_MEMORY_AVAILABLE_PERCENT: - PercentType memoryAvailablePercent = systeminfo.getMemoryAvailablePercent(); - state = (memoryAvailablePercent != null) ? new QuantityType<>(memoryAvailablePercent, Units.PERCENT) - : null; - break; - case CHANNEL_MEMORY_USED_PERCENT: - PercentType memoryUsedPercent = systeminfo.getMemoryUsedPercent(); - state = (memoryUsedPercent != null) ? new QuantityType<>(memoryUsedPercent, Units.PERCENT) : null; - break; - case CHANNEL_SWAP_AVAILABLE: - state = systeminfo.getSwapAvailable(); - break; - case CHANNEL_SWAP_USED: - state = systeminfo.getSwapUsed(); - break; - case CHANNEL_SWAP_TOTAL: - state = systeminfo.getSwapTotal(); - break; - case CHANNEL_SWAP_AVAILABLE_PERCENT: - PercentType swapAvailablePercent = systeminfo.getSwapAvailablePercent(); - state = (swapAvailablePercent != null) ? new QuantityType<>(swapAvailablePercent, Units.PERCENT) - : null; - break; - case CHANNEL_SWAP_USED_PERCENT: - PercentType swapUsedPercent = systeminfo.getSwapUsedPercent(); - state = (swapUsedPercent != null) ? new QuantityType<>(swapUsedPercent, Units.PERCENT) : null; - break; - case CHANNEL_DRIVE_MODEL: - state = systeminfo.getDriveModel(deviceIndex); - break; - case CHANNEL_DRIVE_SERIAL: - state = systeminfo.getDriveSerialNumber(deviceIndex); - break; - case CHANNEL_DRIVE_NAME: - state = systeminfo.getDriveName(deviceIndex); - break; - case CHANNEL_STORAGE_NAME: - state = systeminfo.getStorageName(deviceIndex); - break; - case CHANNEL_STORAGE_DESCRIPTION: - state = systeminfo.getStorageDescription(deviceIndex); - break; - case CHANNEL_STORAGE_AVAILABLE: - state = systeminfo.getStorageAvailable(deviceIndex); - break; - case CHANNEL_STORAGE_USED: - state = systeminfo.getStorageUsed(deviceIndex); - break; - case CHANNEL_STORAGE_TOTAL: - state = systeminfo.getStorageTotal(deviceIndex); - break; - case CHANNEL_STORAGE_TYPE: - state = systeminfo.getStorageType(deviceIndex); - break; - case CHANNEL_STORAGE_AVAILABLE_PERCENT: - PercentType storageAvailablePercent = systeminfo.getStorageAvailablePercent(deviceIndex); - state = (storageAvailablePercent != null) - ? new QuantityType<>(storageAvailablePercent, Units.PERCENT) - : null; - break; - case CHANNEL_STORAGE_USED_PERCENT: - PercentType storageUsedPercent = systeminfo.getStorageUsedPercent(deviceIndex); - state = (storageUsedPercent != null) ? new QuantityType<>(storageUsedPercent, Units.PERCENT) : null; - break; - case CHANNEL_NETWORK_IP: - state = systeminfo.getNetworkIp(deviceIndex); - break; - case CHANNEL_NETWORK_ADAPTER_NAME: - state = systeminfo.getNetworkDisplayName(deviceIndex); - break; - case CHANNEL_NETWORK_NAME: - state = systeminfo.getNetworkName(deviceIndex); - break; - case CHANNEL_NETWORK_MAC: - state = systeminfo.getNetworkMac(deviceIndex); - break; - case CHANNEL_NETWORK_DATA_SENT: - state = systeminfo.getNetworkDataSent(deviceIndex); - break; - case CHANNEL_NETWORK_DATA_RECEIVED: - state = systeminfo.getNetworkDataReceived(deviceIndex); - break; - case CHANNEL_NETWORK_PACKETS_RECEIVED: - state = systeminfo.getNetworkPacketsReceived(deviceIndex); - break; - case CHANNEL_NETWORK_PACKETS_SENT: - state = systeminfo.getNetworkPacketsSent(deviceIndex); - break; - case CHANNEL_PROCESS_LOAD: - 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); - } catch (Exception e) { - logger.debug("Unexpected error occurred while getting system information!", e); - 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 - * first will have deviceIndex=0, the second deviceIndex=1 ant etc). - * When no device index is specified, default value of 0 (first device in the list) is returned. - * - * @param channelID the ID of the channel - * @return natural number (number >=0) - */ - private int getDeviceIndex(ChannelUID channelUID) { - String channelID = channelUID.getId(); - String channelGroupID = channelUID.getGroupId(); - if (channelGroupID == null) { - return 0; - } - - if (channelGroupID.contains(CHANNEL_GROUP_PROCESS)) { - // Only in this case the deviceIndex is part of the channel configuration - PID (Process Identifier) - int pid = getPID(channelUID); - logger.debug("Channel with UID {} tracks process with PID: {}", channelUID, pid); - return pid; - } - - if (channelGroupID.contains(CHANNEL_GROUP_CURRENT_PROCESS)) { - return systeminfo.getCurrentProcessID(); - } - - // 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; - } - - /** - * This method gets the process identifier (PID) for specific process - * - * @param channelUID channel unique identifier - * @return natural number - */ - private int getPID(ChannelUID channelUID) { - int pid = 0; - try { - Channel channel = this.thing.getChannel(channelUID.getId()); - if (channel != null) { - Configuration channelProperties = channel.getConfiguration(); - BigDecimal pidValue = (BigDecimal) channelProperties.get(PID_PARAM); - if (pidValue == null || pidValue.intValue() < 0) { - throw new IllegalArgumentException("Invalid value for Process Identifier."); - } else { - pid = pidValue.intValue(); - } - } else { - 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); - } catch (IllegalArgumentException e) { - logger.debug("PID (Process Identifier) must be positive number. Fall back to default value. ", e); - } - return pid; - } - - @Override - 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); - publishDataForChannel(channelUID); - } else { - logger.debug("Unsupported command {} ! Supported commands: REFRESH", command); - } - } else { - logger.debug("Cannot handle command. Thing is not ONLINE."); - } - } - - private boolean isConfigurationKeyChanged(Configuration currentConfig, Configuration newConfig, String key) { - Object currentValue = currentConfig.get(key); - Object newValue = newConfig.get(key); - - if (currentValue == null) { - return (newValue != null); - } - - return !currentValue.equals(newValue); - } - - @Override - public synchronized void thingUpdated(Thing thing) { - logger.trace("About to update thing"); - boolean isChannelConfigChanged = false; - - List channels = thing.getChannels(); - - for (Channel channel : channels) { - ChannelUID channelUID = channel.getUID(); - Configuration newChannelConfig = channel.getConfiguration(); - Channel oldChannel = this.thing.getChannel(channelUID.getId()); - - if (oldChannel == null) { - logger.warn("Channel with UID {} cannot be updated, as it cannot be found!", channelUID); - continue; - } - Configuration currentChannelConfig = oldChannel.getConfiguration(); - - if (isConfigurationKeyChanged(currentChannelConfig, newChannelConfig, PRIOIRITY_PARAM)) { - isChannelConfigChanged = true; - - handleChannelConfigurationChange(oldChannel, newChannelConfig, PRIOIRITY_PARAM); - - String newPriority = (String) newChannelConfig.get(PRIOIRITY_PARAM); - changeChannelPriority(channelUID, newPriority); - } - - if (isConfigurationKeyChanged(currentChannelConfig, newChannelConfig, PID_PARAM)) { - isChannelConfigChanged = true; - handleChannelConfigurationChange(oldChannel, newChannelConfig, PID_PARAM); - } - } - - if (!(isInitialized() && isChannelConfigChanged)) { - super.thingUpdated(thing); - } - } - - private void handleChannelConfigurationChange(Channel channel, Configuration newConfig, String parameter) { - Configuration configuration = channel.getConfiguration(); - Object oldValue = configuration.get(parameter); - - configuration.put(parameter, newConfig.get(parameter)); - - Object newValue = newConfig.get(parameter); - logger.debug("Channel with UID {} has changed its {} from {} to {}", channel.getUID(), parameter, oldValue, - newValue); - 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!"); - localHighPriorityTasks.cancel(true); - } - - ScheduledFuture localMediumPriorityTasks = mediumPriorityTasks; - if (localMediumPriorityTasks != null) { - logger.debug("Medium prioriy tasks will not be run anymore!"); - localMediumPriorityTasks.cancel(true); - } - } - - @Override - public void dispose() { - stopScheduledUpdates(); - } -} diff --git a/bundles/org.openhab.binding.systeminfo/src/main/java/org/openhab/binding/systeminfo/internal/model/OSHISystemInfo.java b/bundles/org.openhab.binding.systeminfo/src/main/java/org/openhab/binding/systeminfo/internal/model/OSHISystemInfo.java new file mode 100644 index 0000000000..a88c20890f --- /dev/null +++ b/bundles/org.openhab.binding.systeminfo/src/main/java/org/openhab/binding/systeminfo/internal/model/OSHISystemInfo.java @@ -0,0 +1,730 @@ +/** + * Copyright (c) 2010-2024 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.model; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.measure.quantity.ElectricPotential; +import javax.measure.quantity.Frequency; +import javax.measure.quantity.Temperature; +import javax.measure.quantity.Time; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.library.dimension.DataAmount; +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.types.StringType; +import org.openhab.core.library.unit.SIUnits; +import org.openhab.core.library.unit.Units; +import org.osgi.service.component.annotations.Component; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import oshi.SystemInfo; +import oshi.hardware.CentralProcessor; +import oshi.hardware.ComputerSystem; +import oshi.hardware.Display; +import oshi.hardware.GlobalMemory; +import oshi.hardware.HWDiskStore; +import oshi.hardware.HardwareAbstractionLayer; +import oshi.hardware.NetworkIF; +import oshi.hardware.PowerSource; +import oshi.hardware.Sensors; +import oshi.software.os.OSFileStore; +import oshi.software.os.OSProcess; +import oshi.software.os.OperatingSystem; +import oshi.util.EdidUtil; + +/** + * This implementation of {@link SystemInfoInterface} is using the open source library OSHI to provide system + * information. OSHI is a free JNA-based (native) Operating System and Hardware Information library for Java. + * + * @author Svilen Valkanov - Initial contribution + * @author Lyubomir Papazov - Move the initialization logic that could potentially take long time to the + * initializeSystemInfo method + * @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 + * @author Mark Herwege - Use units of measure + * @author Mark Herwege - Processor frequency channels + * + * @see OSHI GitHub repository + */ +@NonNullByDefault +@Component(service = SystemInfoInterface.class) +public class OSHISystemInfo implements SystemInfoInterface { + + private final Logger logger = LoggerFactory.getLogger(OSHISystemInfo.class); + + private @NonNullByDefault({}) HardwareAbstractionLayer hal; + + // Dynamic objects (may be queried repeatedly) + private @NonNullByDefault({}) GlobalMemory memory; + private @NonNullByDefault({}) CentralProcessor cpu; + private @NonNullByDefault({}) Sensors sensors; + + // Static objects, should be recreated on each request + private @NonNullByDefault({}) ComputerSystem computerSystem; + private @NonNullByDefault({}) OperatingSystem operatingSystem; + private @NonNullByDefault({}) List networks; + private @NonNullByDefault({}) List displays; + private @NonNullByDefault({}) List fileStores; + private @NonNullByDefault({}) List powerSources; + private @NonNullByDefault({}) List drives; + + // Array containing cpu tick info to calculate CPU load, according to oshi doc: + // 8 long values representing time spent in User, Nice, System, Idle, IOwait, IRQ, SoftIRQ, and Steal states + private long[] ticks = new long[8]; + // Map containing previous process state to calculate load by process + private Map processTicks = new HashMap<>(); + + public static final int PRECISION_AFTER_DECIMAL_SIGN = 1; + + /** + * Some of the methods used in this constructor execute native code and require execute permissions + * + */ + public OSHISystemInfo() { + logger.debug("OSHISystemInfo service is created"); + } + + @Override + public void initializeSystemInfo() { + logger.debug("OSHISystemInfo service starts initializing"); + + SystemInfo systemInfo = new SystemInfo(); + hal = systemInfo.getHardware(); + + // Doesn't need regular update, they may be queried repeatedly + memory = hal.getMemory(); + cpu = hal.getProcessor(); + sensors = hal.getSensors(); + + computerSystem = hal.getComputerSystem(); + operatingSystem = systemInfo.getOperatingSystem(); + networks = hal.getNetworkIFs(); + displays = hal.getDisplays(); + fileStores = operatingSystem.getFileSystem().getFileStores(); + powerSources = hal.getPowerSources(); + drives = hal.getDiskStores(); + } + + private T getDevice(List<@Nullable T> devices, int index) throws DeviceNotFoundException { + if (devices.size() <= index) { + throw new DeviceNotFoundException("Device with index: " + index + " can not be found!"); + } + return (T) devices.get(index); + } + + private T getDevice(T @Nullable [] devices, int index) throws DeviceNotFoundException { + if (devices == null || devices.length <= index) { + throw new DeviceNotFoundException("Device with index: " + index + " can not be found!"); + } + return devices[index]; + } + + private OSProcess getProcess(int pid) throws DeviceNotFoundException { + OSProcess process = operatingSystem.getProcess(pid); + if (process == null) { + throw new DeviceNotFoundException("Error while getting information for process with PID " + pid); + } + return process; + } + + @Override + public StringType getOsFamily() { + String osFamily = operatingSystem.getFamily(); + return new StringType(osFamily); + } + + @Override + public StringType getOsManufacturer() { + String osManufacturer = operatingSystem.getManufacturer(); + return new StringType(osManufacturer); + } + + @Override + public StringType getOsVersion() { + String osVersion = operatingSystem.getVersionInfo().toString(); + return new StringType(osVersion); + } + + @Override + public StringType getCpuName() { + String name = cpu.getProcessorIdentifier().getName(); + return new StringType(name); + } + + @Override + public StringType getCpuDescription() { + String model = cpu.getProcessorIdentifier().getModel(); + String family = cpu.getProcessorIdentifier().getFamily(); + String serialNumber = computerSystem.getSerialNumber(); + String identifier = cpu.getProcessorIdentifier().getIdentifier(); + String vendor = cpu.getProcessorIdentifier().getVendor(); + String architecture = cpu.getProcessorIdentifier().isCpu64bit() ? "64 bit" : "32 bit"; + String descriptionFormatString = "Model: %s %s,family: %s, vendor: %s, sn: %s, identifier: %s "; + String description = String.format(descriptionFormatString, model, architecture, family, vendor, serialNumber, + identifier); + + return new StringType(description); + } + + @Override + public DecimalType getCpuLogicalCores() { + int logicalProcessorCount = cpu.getLogicalProcessorCount(); + return new DecimalType(logicalProcessorCount); + } + + @Override + public DecimalType getCpuPhysicalCores() { + int physicalProcessorCount = cpu.getPhysicalProcessorCount(); + return new DecimalType(physicalProcessorCount); + } + + @Override + public @Nullable QuantityType getCpuMaxFreq() { + long maxFreq = cpu.getMaxFreq(); + return maxFreq >= 0 ? new QuantityType<>(maxFreq, Units.HERTZ) : null; + } + + @Override + public @Nullable QuantityType getCpuFreq(int logicalProcessorIndex) { + long freq = cpu.getCurrentFreq()[logicalProcessorIndex]; + return freq >= 0 ? new QuantityType<>(freq, Units.HERTZ) : null; + } + + @Override + public QuantityType getMemoryTotal() { + long totalMemory = memory.getTotal(); + totalMemory = getSizeInMB(totalMemory); + return new QuantityType<>(totalMemory, Units.MEBIBYTE); + } + + @Override + public QuantityType getMemoryAvailable() { + long availableMemory = memory.getAvailable(); + availableMemory = getSizeInMB(availableMemory); + return new QuantityType<>(availableMemory, Units.MEBIBYTE); + } + + @Override + public QuantityType getMemoryUsed() { + long totalMemory = memory.getTotal(); + long availableMemory = memory.getAvailable(); + long usedMemory = totalMemory - availableMemory; + usedMemory = getSizeInMB(usedMemory); + return new QuantityType<>(usedMemory, Units.MEBIBYTE); + } + + @Override + public QuantityType getStorageTotal(int index) throws DeviceNotFoundException { + OSFileStore fileStore = getDevice(fileStores, index); + fileStore.updateAttributes(); + long totalSpace = fileStore.getTotalSpace(); + totalSpace = getSizeInMB(totalSpace); + return new QuantityType<>(totalSpace, Units.MEBIBYTE); + } + + @Override + public QuantityType getStorageAvailable(int index) throws DeviceNotFoundException { + OSFileStore fileStore = getDevice(fileStores, index); + fileStore.updateAttributes(); + long freeSpace = fileStore.getUsableSpace(); + freeSpace = getSizeInMB(freeSpace); + return new QuantityType<>(freeSpace, Units.MEBIBYTE); + } + + @Override + public QuantityType getStorageUsed(int index) throws DeviceNotFoundException { + OSFileStore fileStore = getDevice(fileStores, index); + fileStore.updateAttributes(); + long totalSpace = fileStore.getTotalSpace(); + long freeSpace = fileStore.getUsableSpace(); + long usedSpace = totalSpace - freeSpace; + usedSpace = getSizeInMB(usedSpace); + return new QuantityType<>(usedSpace, Units.MEBIBYTE); + } + + @Override + public @Nullable PercentType getStorageAvailablePercent(int deviceIndex) throws DeviceNotFoundException { + OSFileStore fileStore = getDevice(fileStores, deviceIndex); + fileStore.updateAttributes(); + long totalSpace = fileStore.getTotalSpace(); + long freeSpace = fileStore.getUsableSpace(); + if (totalSpace > 0) { + double freePercentDecimal = (double) freeSpace / (double) totalSpace; + BigDecimal freePercent = getPercentsValue(freePercentDecimal); + return new PercentType(freePercent); + } else { + return null; + } + } + + @Override + public @Nullable PercentType getStorageUsedPercent(int deviceIndex) throws DeviceNotFoundException { + OSFileStore fileStore = getDevice(fileStores, deviceIndex); + fileStore.updateAttributes(); + long totalSpace = fileStore.getTotalSpace(); + long freeSpace = fileStore.getUsableSpace(); + long usedSpace = totalSpace - freeSpace; + if (totalSpace > 0) { + double usedPercentDecimal = (double) usedSpace / (double) totalSpace; + BigDecimal usedPercent = getPercentsValue(usedPercentDecimal); + return new PercentType(usedPercent); + } else { + return null; + } + } + + @Override + public StringType getStorageName(int index) throws DeviceNotFoundException { + OSFileStore fileStore = getDevice(fileStores, index); + String name = fileStore.getName(); + return new StringType(name); + } + + @Override + public StringType getStorageType(int deviceIndex) throws DeviceNotFoundException { + OSFileStore fileStore = getDevice(fileStores, deviceIndex); + String type = fileStore.getType(); + return new StringType(type); + } + + @Override + public StringType getStorageDescription(int index) throws DeviceNotFoundException { + OSFileStore fileStore = getDevice(fileStores, index); + String description = fileStore.getDescription(); + return new StringType(description); + } + + @Override + public StringType getNetworkIp(int index) throws DeviceNotFoundException { + NetworkIF netInterface = getDevice(networks, index); + netInterface.updateAttributes(); + String[] ipAddresses = netInterface.getIPv4addr(); + String ipv4 = getDevice(ipAddresses, 0); + return new StringType(ipv4); + } + + @Override + public StringType getNetworkName(int index) throws DeviceNotFoundException { + NetworkIF netInterface = getDevice(networks, index); + String name = netInterface.getName(); + return new StringType(name); + } + + @Override + public StringType getNetworkDisplayName(int index) throws DeviceNotFoundException { + NetworkIF netInterface = getDevice(networks, index); + String adapterName = netInterface.getDisplayName(); + return new StringType(adapterName); + } + + @Override + public StringType getDisplayInformation(int index) throws DeviceNotFoundException { + Display display = getDevice(displays, index); + + byte[] edid = display.getEdid(); + String manufacturer = EdidUtil.getManufacturerID(edid); + String product = EdidUtil.getProductID(edid); + String serialNumber = EdidUtil.getSerialNo(edid); + int width = EdidUtil.getHcm(edid); + int height = EdidUtil.getVcm(edid); + + String edidFormatString = "Product %s, manufacturer %s, SN: %s, Width: %d, Height: %d"; + String edidInfo = String.format(edidFormatString, product, manufacturer, serialNumber, width, height); + return new StringType(edidInfo); + } + + @Override + public @Nullable QuantityType getSensorsCpuTemperature() { + BigDecimal cpuTemp = new BigDecimal(sensors.getCpuTemperature()); + cpuTemp = cpuTemp.setScale(PRECISION_AFTER_DECIMAL_SIGN, RoundingMode.HALF_UP); + return cpuTemp.signum() == 1 ? new QuantityType<>(cpuTemp, SIUnits.CELSIUS) : null; + } + + @Override + public @Nullable QuantityType getSensorsCpuVoltage() { + BigDecimal cpuVoltage = new BigDecimal(sensors.getCpuVoltage()); + cpuVoltage = cpuVoltage.setScale(PRECISION_AFTER_DECIMAL_SIGN, RoundingMode.HALF_UP); + return cpuVoltage.signum() == 1 ? new QuantityType<>(cpuVoltage, Units.VOLT) : null; + } + + @Override + public @Nullable DecimalType getSensorsFanSpeed(int index) throws DeviceNotFoundException { + int[] fanSpeeds = sensors.getFanSpeeds(); + 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; + } + + @Override + public @Nullable QuantityType