]> git.basschouten.com Git - openhab-addons.git/commitdiff
[systeminfo] Rename Systeminfo* to SystemInfo* (#16823)
authorAlexander Falkenstern <falkena@users.noreply.github.com>
Fri, 31 May 2024 08:11:51 +0000 (10:11 +0200)
committerGitHub <noreply@github.com>
Fri, 31 May 2024 08:11:51 +0000 (10:11 +0200)
Signed-off-by: Alexander Falkenstern <alexander.falkenstern@gmail.com>
18 files changed:
bundles/org.openhab.binding.systeminfo/pom.xml
bundles/org.openhab.binding.systeminfo/src/main/java/org/openhab/binding/systeminfo/internal/SystemInfoBindingConstants.java [new file with mode: 0644]
bundles/org.openhab.binding.systeminfo/src/main/java/org/openhab/binding/systeminfo/internal/SystemInfoHandlerFactory.java [new file with mode: 0644]
bundles/org.openhab.binding.systeminfo/src/main/java/org/openhab/binding/systeminfo/internal/SystemInfoThingTypeProvider.java [new file with mode: 0644]
bundles/org.openhab.binding.systeminfo/src/main/java/org/openhab/binding/systeminfo/internal/SysteminfoBindingConstants.java [deleted file]
bundles/org.openhab.binding.systeminfo/src/main/java/org/openhab/binding/systeminfo/internal/SysteminfoHandlerFactory.java [deleted file]
bundles/org.openhab.binding.systeminfo/src/main/java/org/openhab/binding/systeminfo/internal/SysteminfoThingTypeProvider.java [deleted file]
bundles/org.openhab.binding.systeminfo/src/main/java/org/openhab/binding/systeminfo/internal/discovery/SystemInfoDiscoveryService.java [new file with mode: 0644]
bundles/org.openhab.binding.systeminfo/src/main/java/org/openhab/binding/systeminfo/internal/discovery/SysteminfoDiscoveryService.java [deleted file]
bundles/org.openhab.binding.systeminfo/src/main/java/org/openhab/binding/systeminfo/internal/handler/SystemInfoHandler.java [new file with mode: 0644]
bundles/org.openhab.binding.systeminfo/src/main/java/org/openhab/binding/systeminfo/internal/handler/SysteminfoHandler.java [deleted file]
bundles/org.openhab.binding.systeminfo/src/main/java/org/openhab/binding/systeminfo/internal/model/OSHISystemInfo.java [new file with mode: 0644]
bundles/org.openhab.binding.systeminfo/src/main/java/org/openhab/binding/systeminfo/internal/model/OSHISysteminfo.java [deleted file]
bundles/org.openhab.binding.systeminfo/src/main/java/org/openhab/binding/systeminfo/internal/model/SystemInfoInterface.java [new file with mode: 0644]
bundles/org.openhab.binding.systeminfo/src/main/java/org/openhab/binding/systeminfo/internal/model/SysteminfoInterface.java [deleted file]
itests/org.openhab.binding.systeminfo.tests/pom.xml
itests/org.openhab.binding.systeminfo.tests/src/main/java/org/openhab/binding/systeminfo/test/SystemInfoOSGiTest.java [new file with mode: 0644]
itests/org.openhab.binding.systeminfo.tests/src/main/java/org/openhab/binding/systeminfo/test/SysteminfoOSGiTest.java [deleted file]

index 6e8f2d00c90d7561d04659c148b4a8248004ecfe..2e164819316543eeba16c83457bb516423cfc558 100644 (file)
@@ -12,7 +12,7 @@
 
   <artifactId>org.openhab.binding.systeminfo</artifactId>
 
-  <name>openHAB Add-ons :: Bundles :: Systeminfo Binding</name>
+  <name>openHAB Add-ons :: Bundles :: SystemInfo Binding</name>
 
   <dependencies>
     <dependency>
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 (file)
index 0000000..71a2e8d
--- /dev/null
@@ -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 (file)
index 0000000..0b4db4c
--- /dev/null
@@ -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 (file)
index 0000000..93da1c1
--- /dev/null
@@ -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<ThingUID, Map<String, Configuration>> 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<ChannelGroupDefinition> 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<ChannelGroupDefinition> getChannelGroupDefinitions(ThingTypeUID typeUID) {
+        ThingType type = thingTypeRegistry.getThingType(typeUID);
+        if (type == null) {
+            type = thingTypeRegistry.getThingType(THING_TYPE_COMPUTER);
+        }
+        if (type != null) {
+            return type.getChannelGroupDefinitions();
+        } else {
+            logger.debug("Cannot retrieve channel group definitions, no base thing type found");
+            return Collections.emptyList();
+        }
+    }
+
+    /**
+     * Create a new channel group definition with index appended to id and label.
+     *
+     * @param channelGroupID id of channel group without index
+     * @param channelGroupTypeID id ChannelGroupType for new channel group definition
+     * @param i index
+     * @return channel group definition, null if provided channelGroupTypeID cannot be found in ChannelGroupTypeRegistry
+     */
+    public @Nullable ChannelGroupDefinition createChannelGroupDefinitionWithIndex(String channelGroupID,
+            String channelGroupTypeID, int i) {
+        ChannelGroupTypeUID channelGroupTypeUID = new ChannelGroupTypeUID(BINDING_ID, channelGroupTypeID);
+        ChannelGroupType channelGroupType = channelGroupTypeRegistry.getChannelGroupType(channelGroupTypeUID);
+        if (channelGroupType == null) {
+            logger.debug("Cannot create channel group definition, group type {} invalid", channelGroupTypeID);
+            return null;
+        }
+        String index = String.valueOf(i);
+        return new ChannelGroupDefinition(channelGroupID + index, channelGroupTypeUID,
+                channelGroupType.getLabel() + " " + index, channelGroupType.getDescription());
+    }
+
+    /**
+     * Create a new channel with index appended to id and label of an existing channel.
+     *
+     * @param thing containing the existing channel
+     * @param channelID id of channel without index
+     * @param i index
+     * @return channel, null if provided channelID does not match a channel, or no type can be retrieved for the
+     *         provided channel
+     */
+    public @Nullable Channel createChannelWithIndex(Thing thing, String channelID, int i) {
+        Channel baseChannel = thing.getChannel(channelID);
+        if (baseChannel == null) {
+            logger.debug("Cannot create channel, ID {} invalid", channelID);
+            return null;
+        }
+        ChannelTypeUID channelTypeUID = baseChannel.getChannelTypeUID();
+        ChannelType channelType = channelTypeRegistry.getChannelType(channelTypeUID);
+        if (channelType == null) {
+            logger.debug("Cannot create channel, type {} invalid",
+                    channelTypeUID != null ? channelTypeUID.getId() : "null");
+            return null;
+        }
+        ThingUID thingUID = thing.getUID();
+        String index = String.valueOf(i);
+        ChannelUID channelUID = new ChannelUID(thingUID, channelID + index);
+        ChannelBuilder builder = ChannelBuilder.create(channelUID).withType(channelTypeUID)
+                .withConfiguration(baseChannel.getConfiguration());
+        builder.withLabel(channelType.getLabel() + " " + index);
+        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<String, Configuration> channelsConfig = thing.getChannels().stream()
+                .collect(Collectors.toMap(c -> c.getUID().getId(), c -> c.getConfiguration()));
+        thingChannelsConfig.put(thing.getUID(), channelsConfig);
+    }
+
+    /**
+     * Restore previous channel configurations of matching channels when the thing handler gets recreated with a new
+     * thing type. Return an empty map if no channel configurations where stored. Before returning previous channel
+     * configurations, clear the store, so they can only be retrieved ones, immediately after a thing type change. See
+     * also {@link #storeChannelsConfig(Thing)}.
+     *
+     * @param UID
+     * @return Map of ChannelId and Configuration for the channel
+     */
+    public Map<String, Configuration> restoreChannelsConfig(ThingUID UID) {
+        Map<String, Configuration> configs = thingChannelsConfig.remove(UID);
+        return configs != null ? configs : Collections.emptyMap();
+    }
+}
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 (file)
index ebe6c7a..0000000
+++ /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 (file)
index ded9260..0000000
+++ /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 (file)
index 20ab103..0000000
+++ /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<ThingUID, Map<String, Configuration>> 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<ChannelGroupDefinition> 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<ChannelGroupDefinition> getChannelGroupDefinitions(ThingTypeUID typeUID) {
-        ThingType type = thingTypeRegistry.getThingType(typeUID);
-        if (type == null) {
-            type = thingTypeRegistry.getThingType(THING_TYPE_COMPUTER);
-        }
-        if (type != null) {
-            return type.getChannelGroupDefinitions();
-        } else {
-            logger.debug("Cannot retrieve channel group definitions, no base thing type found");
-            return Collections.emptyList();
-        }
-    }
-
-    /**
-     * Create a new channel group definition with index appended to id and label.
-     *
-     * @param channelGroupID id of channel group without index
-     * @param channelGroupTypeID id ChannelGroupType for new channel group definition
-     * @param i index
-     * @return channel group definition, null if provided channelGroupTypeID cannot be found in ChannelGroupTypeRegistry
-     */
-    public @Nullable ChannelGroupDefinition createChannelGroupDefinitionWithIndex(String channelGroupID,
-            String channelGroupTypeID, int i) {
-        ChannelGroupTypeUID channelGroupTypeUID = new ChannelGroupTypeUID(BINDING_ID, channelGroupTypeID);
-        ChannelGroupType channelGroupType = channelGroupTypeRegistry.getChannelGroupType(channelGroupTypeUID);
-        if (channelGroupType == null) {
-            logger.debug("Cannot create channel group definition, group type {} invalid", channelGroupTypeID);
-            return null;
-        }
-        String index = String.valueOf(i);
-        return new ChannelGroupDefinition(channelGroupID + index, channelGroupTypeUID,
-                channelGroupType.getLabel() + " " + index, channelGroupType.getDescription());
-    }
-
-    /**
-     * Create a new channel with index appended to id and label of an existing channel.
-     *
-     * @param thing containing the existing channel
-     * @param channelID id of channel without index
-     * @param i index
-     * @return channel, null if provided channelID does not match a channel, or no type can be retrieved for the
-     *         provided channel
-     */
-    public @Nullable Channel createChannelWithIndex(Thing thing, String channelID, int i) {
-        Channel baseChannel = thing.getChannel(channelID);
-        if (baseChannel == null) {
-            logger.debug("Cannot create channel, ID {} invalid", channelID);
-            return null;
-        }
-        ChannelTypeUID channelTypeUID = baseChannel.getChannelTypeUID();
-        ChannelType channelType = channelTypeRegistry.getChannelType(channelTypeUID);
-        if (channelType == null) {
-            logger.debug("Cannot create channel, type {} invalid",
-                    channelTypeUID != null ? channelTypeUID.getId() : "null");
-            return null;
-        }
-        ThingUID thingUID = thing.getUID();
-        String index = String.valueOf(i);
-        ChannelUID channelUID = new ChannelUID(thingUID, channelID + index);
-        ChannelBuilder builder = ChannelBuilder.create(channelUID).withType(channelTypeUID)
-                .withConfiguration(baseChannel.getConfiguration());
-        builder.withLabel(channelType.getLabel() + " " + index);
-        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<String, Configuration> channelsConfig = thing.getChannels().stream()
-                .collect(Collectors.toMap(c -> c.getUID().getId(), c -> c.getConfiguration()));
-        thingChannelsConfig.put(thing.getUID(), channelsConfig);
-    }
-
-    /**
-     * Restore previous channel configurations of matching channels when the thing handler gets recreated with a new
-     * thing type. Return an empty map if no channel configurations where stored. Before returning previous channel
-     * configurations, clear the store, so they can only be retrieved ones, immediately after a thing type change. See
-     * also {@link #storeChannelsConfig(Thing)}.
-     *
-     * @param UID
-     * @return Map of ChannelId and Configuration for the channel
-     */
-    public Map<String, Configuration> restoreChannelsConfig(ThingUID UID) {
-        Map<String, Configuration> configs = thingChannelsConfig.remove(UID);
-        return configs != null ? configs : Collections.emptyMap();
-    }
-}
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 (file)
index 0000000..93a73f4
--- /dev/null
@@ -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<ThingTypeUID> 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 (file)
index b2c90cf..0000000
+++ /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<ThingTypeUID> 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 (file)
index 0000000..366ee94
--- /dev/null
@@ -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<ChannelUID> 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<ChannelUID> 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<ChannelUID> 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<PercentType> cpuLoadCache = new ExpiringCache<>(MIN_PROCESS_LOAD_REFRESH_INTERVAL_MS,
+            () -> getSystemCpuLoad());
+    private ExpiringCacheMap<Integer, @Nullable DecimalType> processLoadCache = new ExpiringCacheMap<>(
+            MIN_PROCESS_LOAD_REFRESH_INTERVAL_MS);
+
+    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<String, String> 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.
+     * <p>
+     * If channel groups are added, the thing type will change to systeminfo:computer-Ext, with Ext equal to the thing
+     * id. A new handler will be created and initialization restarted. Therefore further initialization of the current
+     * handler can be aborted if the method returns true.
+     *
+     * @return true if channel groups where added
+     */
+    private boolean addDynamicChannels() {
+        ThingUID thingUID = thing.getUID();
+
+        List<ChannelGroupDefinition> newChannelGroups = new ArrayList<>();
+        newChannelGroups.addAll(createChannelGroups(thingUID, CHANNEL_GROUP_STORAGE, CHANNEL_GROUP_TYPE_STORAGE,
+                systeminfo.getFileOSStoreCount()));
+        newChannelGroups.addAll(createChannelGroups(thingUID, CHANNEL_GROUP_DRIVE, CHANNEL_GROUP_TYPE_DRIVE,
+                systeminfo.getDriveCount()));
+        newChannelGroups.addAll(createChannelGroups(thingUID, CHANNEL_GROUP_DISPLAY, CHANNEL_GROUP_TYPE_DISPLAY,
+                systeminfo.getDisplayCount()));
+        newChannelGroups.addAll(createChannelGroups(thingUID, CHANNEL_GROUP_BATTERY, CHANNEL_GROUP_TYPE_BATTERY,
+                systeminfo.getPowerSourceCount()));
+        newChannelGroups.addAll(createChannelGroups(thingUID, CHANNEL_GROUP_NETWORK, CHANNEL_GROUP_TYPE_NETWORK,
+                systeminfo.getNetworkIFCount()));
+        if (!newChannelGroups.isEmpty()) {
+            logger.debug("Creating additional channel groups");
+            newChannelGroups.addAll(0, thingTypeProvider.getChannelGroupDefinitions(thing.getThingTypeUID()));
+            ThingTypeUID thingTypeUID = new ThingTypeUID(BINDING_ID, THING_TYPE_COMPUTER_ID + idExtString);
+            if (thingTypeProvider.updateThingType(thingTypeUID, newChannelGroups)) {
+                logger.trace("Channel groups were added, changing the thing type");
+                changeThingType(thingTypeUID, thing.getConfiguration());
+            } else {
+                updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.HANDLER_INITIALIZING_ERROR,
+                        "@text/offline.cannot-initialize");
+            }
+            return true;
+        }
+
+        List<Channel> newChannels = new ArrayList<>();
+        newChannels.addAll(createChannels(thingUID, CHANNEL_SENSORS_FAN_SPEED, systeminfo.getFanCount()));
+        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<ChannelGroupDefinition> createChannelGroups(ThingUID thingUID, String channelGroupID,
+            String channelGroupTypeID, int count) {
+        if (count <= 1) {
+            return Collections.emptyList();
+        }
+
+        List<String> channelGroups = thingTypeProvider.getChannelGroupDefinitions(thing.getThingTypeUID()).stream()
+                .map(ChannelGroupDefinition::getId).collect(Collectors.toList());
+
+        List<ChannelGroupDefinition> newChannelGroups = new ArrayList<>();
+        for (int i = 0; i < count; i++) {
+            String index = String.valueOf(i);
+            ChannelGroupDefinition channelGroupDef = thingTypeProvider
+                    .createChannelGroupDefinitionWithIndex(channelGroupID, channelGroupTypeID, i);
+            if (!(channelGroupDef == null || channelGroups.contains(channelGroupID + index))) {
+                logger.trace("Adding channel group {}", channelGroupID + index);
+                newChannelGroups.add(channelGroupDef);
+            }
+        }
+        return newChannelGroups;
+    }
+
+    private List<Channel> createChannels(ThingUID thingUID, String channelID, int count) {
+        if (count <= 1) {
+            return Collections.emptyList();
+        }
+
+        List<Channel> newChannels = new ArrayList<>();
+        for (int i = 0; i < count; i++) {
+            Channel channel = thingTypeProvider.createChannelWithIndex(thing, channelID, i);
+            if (channel != null && thing.getChannel(channel.getUID()) == null) {
+                logger.trace("Creating channel {}", channel.getUID().getId());
+                newChannels.add(channel);
+            }
+        }
+        return newChannels;
+    }
+
+    private void storeChannelsConfig() {
+        logger.trace("Storing channel configurations");
+        thingTypeProvider.storeChannelsConfig(thing);
+    }
+
+    private void restoreChannelsConfig() {
+        logger.trace("Restoring channel configurations");
+        Map<String, Configuration> channelsConfig = thingTypeProvider.restoreChannelsConfig(thing.getUID());
+        for (String channelId : channelsConfig.keySet()) {
+            Channel channel = thing.getChannel(channelId);
+            Configuration config = channelsConfig.get(channelId);
+            if (channel != null && config != null) {
+                Configuration currentConfig = channel.getConfiguration();
+                for (String param : config.keySet()) {
+                    if (isConfigurationKeyChanged(currentConfig, config, param)) {
+                        handleChannelConfigurationChange(channel, config, param);
+                    }
+                }
+            }
+        }
+    }
+
+    private void groupChannelsByPriority() {
+        logger.trace("Grouping channels by priority");
+        List<Channel> channels = this.thing.getChannels();
+
+        for (Channel channel : channels) {
+            Configuration properties = channel.getConfiguration();
+            String priority = (String) properties.get(PRIOIRITY_PARAM);
+            if (priority == null) {
+                logger.debug("Channel with UID {} will not be updated. The channel has no priority set!",
+                        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<ChannelUID> 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<ChannelUID> getHighPriorityChannels() {
+        return highPriorityChannels;
+    }
+
+    public Set<ChannelUID> getMediumPriorityChannels() {
+        return mediumPriorityChannels;
+    }
+
+    public Set<ChannelUID> 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<Channel> 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 (file)
index eb6fccc..0000000
+++ /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<ChannelUID> 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<ChannelUID> 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<ChannelUID> 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<PercentType> cpuLoadCache = new ExpiringCache<>(MIN_PROCESS_LOAD_REFRESH_INTERVAL_MS,
-            () -> getSystemCpuLoad());
-    private ExpiringCacheMap<Integer, @Nullable DecimalType> processLoadCache = new ExpiringCacheMap<>(
-            MIN_PROCESS_LOAD_REFRESH_INTERVAL_MS);
-
-    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<String, String> 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.
-     * <p>
-     * If channel groups are added, the thing type will change to systeminfo:computer-Ext, with Ext equal to the thing
-     * id. A new handler will be created and initialization restarted. Therefore further initialization of the current
-     * handler can be aborted if the method returns true.
-     *
-     * @return true if channel groups where added
-     */
-    private boolean addDynamicChannels() {
-        ThingUID thingUID = thing.getUID();
-
-        List<ChannelGroupDefinition> newChannelGroups = new ArrayList<>();
-        newChannelGroups.addAll(createChannelGroups(thingUID, CHANNEL_GROUP_STORAGE, CHANNEL_GROUP_TYPE_STORAGE,
-                systeminfo.getFileOSStoreCount()));
-        newChannelGroups.addAll(createChannelGroups(thingUID, CHANNEL_GROUP_DRIVE, CHANNEL_GROUP_TYPE_DRIVE,
-                systeminfo.getDriveCount()));
-        newChannelGroups.addAll(createChannelGroups(thingUID, CHANNEL_GROUP_DISPLAY, CHANNEL_GROUP_TYPE_DISPLAY,
-                systeminfo.getDisplayCount()));
-        newChannelGroups.addAll(createChannelGroups(thingUID, CHANNEL_GROUP_BATTERY, CHANNEL_GROUP_TYPE_BATTERY,
-                systeminfo.getPowerSourceCount()));
-        newChannelGroups.addAll(createChannelGroups(thingUID, CHANNEL_GROUP_NETWORK, CHANNEL_GROUP_TYPE_NETWORK,
-                systeminfo.getNetworkIFCount()));
-        if (!newChannelGroups.isEmpty()) {
-            logger.debug("Creating additional channel groups");
-            newChannelGroups.addAll(0, thingTypeProvider.getChannelGroupDefinitions(thing.getThingTypeUID()));
-            ThingTypeUID thingTypeUID = new ThingTypeUID(BINDING_ID, THING_TYPE_COMPUTER_ID + idExtString);
-            if (thingTypeProvider.updateThingType(thingTypeUID, newChannelGroups)) {
-                logger.trace("Channel groups were added, changing the thing type");
-                changeThingType(thingTypeUID, thing.getConfiguration());
-            } else {
-                updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.HANDLER_INITIALIZING_ERROR,
-                        "@text/offline.cannot-initialize");
-            }
-            return true;
-        }
-
-        List<Channel> newChannels = new ArrayList<>();
-        newChannels.addAll(createChannels(thingUID, CHANNEL_SENSORS_FAN_SPEED, systeminfo.getFanCount()));
-        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<ChannelGroupDefinition> createChannelGroups(ThingUID thingUID, String channelGroupID,
-            String channelGroupTypeID, int count) {
-        if (count <= 1) {
-            return Collections.emptyList();
-        }
-
-        List<String> channelGroups = thingTypeProvider.getChannelGroupDefinitions(thing.getThingTypeUID()).stream()
-                .map(ChannelGroupDefinition::getId).collect(Collectors.toList());
-
-        List<ChannelGroupDefinition> newChannelGroups = new ArrayList<>();
-        for (int i = 0; i < count; i++) {
-            String index = String.valueOf(i);
-            ChannelGroupDefinition channelGroupDef = thingTypeProvider
-                    .createChannelGroupDefinitionWithIndex(channelGroupID, channelGroupTypeID, i);
-            if (!(channelGroupDef == null || channelGroups.contains(channelGroupID + index))) {
-                logger.trace("Adding channel group {}", channelGroupID + index);
-                newChannelGroups.add(channelGroupDef);
-            }
-        }
-        return newChannelGroups;
-    }
-
-    private List<Channel> createChannels(ThingUID thingUID, String channelID, int count) {
-        if (count <= 1) {
-            return Collections.emptyList();
-        }
-
-        List<Channel> newChannels = new ArrayList<>();
-        for (int i = 0; i < count; i++) {
-            Channel channel = thingTypeProvider.createChannelWithIndex(thing, channelID, i);
-            if (channel != null && thing.getChannel(channel.getUID()) == null) {
-                logger.trace("Creating channel {}", channel.getUID().getId());
-                newChannels.add(channel);
-            }
-        }
-        return newChannels;
-    }
-
-    private void storeChannelsConfig() {
-        logger.trace("Storing channel configurations");
-        thingTypeProvider.storeChannelsConfig(thing);
-    }
-
-    private void restoreChannelsConfig() {
-        logger.trace("Restoring channel configurations");
-        Map<String, Configuration> channelsConfig = thingTypeProvider.restoreChannelsConfig(thing.getUID());
-        for (String channelId : channelsConfig.keySet()) {
-            Channel channel = thing.getChannel(channelId);
-            Configuration config = channelsConfig.get(channelId);
-            if (channel != null && config != null) {
-                Configuration currentConfig = channel.getConfiguration();
-                for (String param : config.keySet()) {
-                    if (isConfigurationKeyChanged(currentConfig, config, param)) {
-                        handleChannelConfigurationChange(channel, config, param);
-                    }
-                }
-            }
-        }
-    }
-
-    private void groupChannelsByPriority() {
-        logger.trace("Grouping channels by priority");
-        List<Channel> channels = this.thing.getChannels();
-
-        for (Channel channel : channels) {
-            Configuration properties = channel.getConfiguration();
-            String priority = (String) properties.get(PRIOIRITY_PARAM);
-            if (priority == null) {
-                logger.debug("Channel with UID {} will not be updated. The channel has no priority set!",
-                        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<ChannelUID> 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<ChannelUID> getHighPriorityChannels() {
-        return highPriorityChannels;
-    }
-
-    public Set<ChannelUID> getMediumPriorityChannels() {
-        return mediumPriorityChannels;
-    }
-
-    public Set<ChannelUID> 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<Channel> 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 (file)
index 0000000..a88c208
--- /dev/null
@@ -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 <a href="https://github.com/oshi/oshi">OSHI GitHub repository</a>
+ */
+@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<NetworkIF> networks;
+    private @NonNullByDefault({}) List<Display> displays;
+    private @NonNullByDefault({}) List<OSFileStore> fileStores;
+    private @NonNullByDefault({}) List<PowerSource> powerSources;
+    private @NonNullByDefault({}) List<HWDiskStore> 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<Integer, OSProcess> 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> 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> 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<Frequency> getCpuMaxFreq() {
+        long maxFreq = cpu.getMaxFreq();
+        return maxFreq >= 0 ? new QuantityType<>(maxFreq, Units.HERTZ) : null;
+    }
+
+    @Override
+    public @Nullable QuantityType<Frequency> getCpuFreq(int logicalProcessorIndex) {
+        long freq = cpu.getCurrentFreq()[logicalProcessorIndex];
+        return freq >= 0 ? new QuantityType<>(freq, Units.HERTZ) : null;
+    }
+
+    @Override
+    public QuantityType<DataAmount> getMemoryTotal() {
+        long totalMemory = memory.getTotal();
+        totalMemory = getSizeInMB(totalMemory);
+        return new QuantityType<>(totalMemory, Units.MEBIBYTE);
+    }
+
+    @Override
+    public QuantityType<DataAmount> getMemoryAvailable() {
+        long availableMemory = memory.getAvailable();
+        availableMemory = getSizeInMB(availableMemory);
+        return new QuantityType<>(availableMemory, Units.MEBIBYTE);
+    }
+
+    @Override
+    public QuantityType<DataAmount> 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<DataAmount> 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<DataAmount> 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<DataAmount> 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<Temperature> 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<ElectricPotential> 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<Time> getBatteryRemainingTime(int index) throws DeviceNotFoundException {
+        PowerSource powerSource = getDevice(powerSources, index);
+        powerSource.updateAttributes();
+        double remainingTimeInSeconds = powerSource.getTimeRemainingEstimated();
+        // The getTimeRemaining() method returns (-1.0) if is calculating or (-2.0) if the time is unlimited.
+        BigDecimal remainingTime = getTimeInMinutes(remainingTimeInSeconds);
+        return remainingTime.signum() == 1 ? new QuantityType<>(remainingTime, Units.MINUTE) : null;
+    }
+
+    @Override
+    public PercentType getBatteryRemainingCapacity(int index) throws DeviceNotFoundException {
+        PowerSource powerSource = getDevice(powerSources, index);
+        powerSource.updateAttributes();
+        double remainingCapacity = powerSource.getRemainingCapacityPercent();
+        BigDecimal remainingCapacityPercents = getPercentsValue(remainingCapacity);
+        return new PercentType(remainingCapacityPercents);
+    }
+
+    @Override
+    public StringType getBatteryName(int index) throws DeviceNotFoundException {
+        PowerSource powerSource = getDevice(powerSources, index);
+        String name = powerSource.getName();
+        return new StringType(name);
+    }
+
+    @Override
+    public @Nullable PercentType getMemoryAvailablePercent() {
+        long availableMemory = memory.getAvailable();
+        long totalMemory = memory.getTotal();
+        if (totalMemory > 0) {
+            double freePercentDecimal = (double) availableMemory / (double) totalMemory;
+            BigDecimal freePercent = getPercentsValue(freePercentDecimal);
+            return new PercentType(freePercent);
+        } else {
+            return null;
+        }
+    }
+
+    @Override
+    public @Nullable PercentType getMemoryUsedPercent() {
+        long availableMemory = memory.getAvailable();
+        long totalMemory = memory.getTotal();
+        long usedMemory = totalMemory - availableMemory;
+        if (totalMemory > 0) {
+            double usedPercentDecimal = (double) usedMemory / (double) totalMemory;
+            BigDecimal usedPercent = getPercentsValue(usedPercentDecimal);
+            return new PercentType(usedPercent);
+        } else {
+            return null;
+        }
+    }
+
+    @Override
+    public StringType getDriveName(int deviceIndex) throws DeviceNotFoundException {
+        HWDiskStore drive = getDevice(drives, deviceIndex);
+        String name = drive.getName();
+        return new StringType(name);
+    }
+
+    @Override
+    public StringType getDriveModel(int deviceIndex) throws DeviceNotFoundException {
+        HWDiskStore drive = getDevice(drives, deviceIndex);
+        String model = drive.getModel();
+        return new StringType(model);
+    }
+
+    @Override
+    public StringType getDriveSerialNumber(int deviceIndex) throws DeviceNotFoundException {
+        HWDiskStore drive = getDevice(drives, deviceIndex);
+        String serialNumber = drive.getSerial();
+        return new StringType(serialNumber);
+    }
+
+    @Override
+    public QuantityType<DataAmount> getSwapTotal() {
+        long swapTotal = memory.getVirtualMemory().getSwapTotal();
+        swapTotal = getSizeInMB(swapTotal);
+        return new QuantityType<>(swapTotal, Units.MEBIBYTE);
+    }
+
+    @Override
+    public QuantityType<DataAmount> getSwapAvailable() {
+        long swapTotal = memory.getVirtualMemory().getSwapTotal();
+        long swapUsed = memory.getVirtualMemory().getSwapUsed();
+        long swapAvailable = swapTotal - swapUsed;
+        swapAvailable = getSizeInMB(swapAvailable);
+        return new QuantityType<>(swapAvailable, Units.MEBIBYTE);
+    }
+
+    @Override
+    public QuantityType<DataAmount> getSwapUsed() {
+        long swapUsed = memory.getVirtualMemory().getSwapUsed();
+        swapUsed = getSizeInMB(swapUsed);
+        return new QuantityType<>(swapUsed, Units.MEBIBYTE);
+    }
+
+    @Override
+    public @Nullable PercentType getSwapAvailablePercent() {
+        long swapTotal = memory.getVirtualMemory().getSwapTotal();
+        long swapUsed = memory.getVirtualMemory().getSwapUsed();
+        long swapAvailable = swapTotal - swapUsed;
+        if (swapTotal > 0) {
+            double swapAvailablePercentDecimal = (double) swapAvailable / (double) swapTotal;
+            BigDecimal swapAvailablePercent = getPercentsValue(swapAvailablePercentDecimal);
+            return new PercentType(swapAvailablePercent);
+        } else {
+            return null;
+        }
+    }
+
+    @Override
+    public @Nullable PercentType getSwapUsedPercent() {
+        long swapTotal = memory.getVirtualMemory().getSwapTotal();
+        long swapUsed = memory.getVirtualMemory().getSwapUsed();
+        if (swapTotal > 0) {
+            double swapUsedPercentDecimal = (double) swapUsed / (double) swapTotal;
+            BigDecimal swapUsedPercent = getPercentsValue(swapUsedPercentDecimal);
+            return new PercentType(swapUsedPercent);
+        } else {
+            return null;
+        }
+    }
+
+    private long getSizeInMB(long sizeInBytes) {
+        return Math.round(sizeInBytes / (1024D * 1024));
+    }
+
+    private BigDecimal getPercentsValue(double decimalFraction) {
+        BigDecimal result = new BigDecimal(decimalFraction * 100);
+        result = result.setScale(PRECISION_AFTER_DECIMAL_SIGN, RoundingMode.HALF_UP);
+        return result;
+    }
+
+    private BigDecimal getTimeInMinutes(double timeInSeconds) {
+        BigDecimal timeInMinutes = new BigDecimal(timeInSeconds / 60);
+        timeInMinutes = timeInMinutes.setScale(PRECISION_AFTER_DECIMAL_SIGN, RoundingMode.UP);
+        return timeInMinutes;
+    }
+
+    @Override
+    public @Nullable PercentType getSystemCpuLoad() {
+        PercentType load = (ticks[0] > 0) ? new PercentType(getPercentsValue(cpu.getSystemCpuLoadBetweenTicks(ticks)))
+                : null;
+        ticks = cpu.getSystemCpuLoadTicks();
+        return load;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * This information is available only on Mac and Linux OS.
+     */
+    @Override
+    public @Nullable DecimalType getCpuLoad1() {
+        BigDecimal avarageCpuLoad = getAvarageCpuLoad(1);
+        return avarageCpuLoad.signum() == -1 ? null : new DecimalType(avarageCpuLoad);
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * This information is available only on Mac and Linux OS.
+     */
+    @Override
+    public @Nullable DecimalType getCpuLoad5() {
+        BigDecimal avarageCpuLoad = getAvarageCpuLoad(5);
+        return avarageCpuLoad.signum() == -1 ? null : new DecimalType(avarageCpuLoad);
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * This information is available only on Mac and Linux OS.
+     */
+    @Override
+    public @Nullable DecimalType getCpuLoad15() {
+        BigDecimal avarageCpuLoad = getAvarageCpuLoad(15);
+        return avarageCpuLoad.signum() == -1 ? null : new DecimalType(avarageCpuLoad);
+    }
+
+    private BigDecimal getAvarageCpuLoad(int timeInMinutes) {
+        // This parameter is specified in OSHI Javadoc
+        int index;
+        switch (timeInMinutes) {
+            case 1:
+                index = 0;
+                break;
+            case 5:
+                index = 1;
+                break;
+            case 15:
+                index = 2;
+                break;
+            default:
+                index = 2;
+        }
+        double processorLoads[] = cpu.getSystemLoadAverage(index + 1);
+        BigDecimal result = new BigDecimal(processorLoads[index]);
+        result = result.setScale(PRECISION_AFTER_DECIMAL_SIGN, RoundingMode.HALF_UP);
+        return result;
+    }
+
+    @Override
+    public QuantityType<Time> getCpuUptime() {
+        long seconds = operatingSystem.getSystemUptime();
+        return new QuantityType<>(getTimeInMinutes(seconds), Units.MINUTE);
+    }
+
+    @Override
+    public DecimalType getCpuThreads() {
+        int threadCount = operatingSystem.getThreadCount();
+        return new DecimalType(threadCount);
+    }
+
+    @Override
+    public StringType getNetworkMac(int networkIndex) throws DeviceNotFoundException {
+        NetworkIF network = getDevice(networks, networkIndex);
+        String mac = network.getMacaddr();
+        return new StringType(mac);
+    }
+
+    @Override
+    public DecimalType getNetworkPacketsReceived(int networkIndex) throws DeviceNotFoundException {
+        NetworkIF network = getDevice(networks, networkIndex);
+        network.updateAttributes();
+        long packRecv = network.getPacketsRecv();
+        return new DecimalType(packRecv);
+    }
+
+    @Override
+    public DecimalType getNetworkPacketsSent(int networkIndex) throws DeviceNotFoundException {
+        NetworkIF network = getDevice(networks, networkIndex);
+        network.updateAttributes();
+        long packSent = network.getPacketsSent();
+        return new DecimalType(packSent);
+    }
+
+    @Override
+    public QuantityType<DataAmount> getNetworkDataSent(int networkIndex) throws DeviceNotFoundException {
+        NetworkIF network = getDevice(networks, networkIndex);
+        network.updateAttributes();
+        long bytesSent = network.getBytesSent();
+        return new QuantityType<>(getSizeInMB(bytesSent), Units.MEBIBYTE);
+    }
+
+    @Override
+    public QuantityType<DataAmount> getNetworkDataReceived(int networkIndex) throws DeviceNotFoundException {
+        NetworkIF network = getDevice(networks, networkIndex);
+        network.updateAttributes();
+        long bytesRecv = network.getBytesRecv();
+        return new QuantityType<>(getSizeInMB(bytesRecv), Units.MEBIBYTE);
+    }
+
+    @Override
+    public int getCurrentProcessID() {
+        return operatingSystem.getProcessId();
+    }
+
+    @Override
+    public @Nullable StringType getProcessName(int pid) throws DeviceNotFoundException {
+        if (pid > 0) {
+            OSProcess process = getProcess(pid);
+            String name = process.getName();
+            return new StringType(name);
+        } else {
+            return null;
+        }
+    }
+
+    @Override
+    public @Nullable DecimalType getProcessCpuUsage(int pid) throws DeviceNotFoundException {
+        if (pid > 0) {
+            OSProcess process = getProcess(pid);
+            DecimalType load = (processTicks.containsKey(pid))
+                    ? new DecimalType(getPercentsValue(process.getProcessCpuLoadBetweenTicks(processTicks.get(pid))))
+                    : null;
+            processTicks.put(pid, process);
+            return load;
+        } else {
+            return null;
+        }
+    }
+
+    @Override
+    public @Nullable QuantityType<DataAmount> getProcessMemoryUsage(int pid) throws DeviceNotFoundException {
+        if (pid > 0) {
+            OSProcess process = getProcess(pid);
+            long memortInBytes = process.getResidentSetSize();
+            long memoryInMB = getSizeInMB(memortInBytes);
+            return new QuantityType<>(memoryInMB, Units.MEBIBYTE);
+        } else {
+            return null;
+        }
+    }
+
+    @Override
+    public @Nullable StringType getProcessPath(int pid) throws DeviceNotFoundException {
+        if (pid > 0) {
+            OSProcess process = getProcess(pid);
+            String path = process.getPath();
+            return new StringType(path);
+        } else {
+            return null;
+        }
+    }
+
+    @Override
+    public @Nullable DecimalType getProcessThreads(int pid) throws DeviceNotFoundException {
+        if (pid > 0) {
+            OSProcess process = getProcess(pid);
+            int threadCount = process.getThreadCount();
+            return new DecimalType(threadCount);
+        } else {
+            return null;
+        }
+    }
+
+    @Override
+    public int getNetworkIFCount() {
+        return networks.size();
+    }
+
+    @Override
+    public int getDisplayCount() {
+        return displays.size();
+    }
+
+    @Override
+    public int getFileOSStoreCount() {
+        return fileStores.size();
+    }
+
+    @Override
+    public int getPowerSourceCount() {
+        return powerSources.size();
+    }
+
+    @Override
+    public int getDriveCount() {
+        return drives.size();
+    }
+
+    @Override
+    public int getFanCount() {
+        return sensors.getFanSpeeds().length;
+    }
+}
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
deleted file mode 100644 (file)
index 2277d59..0000000
+++ /dev/null
@@ -1,730 +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.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 <a href="https://github.com/oshi/oshi">OSHI GitHub repository</a>
- */
-@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<NetworkIF> networks;
-    private @NonNullByDefault({}) List<Display> displays;
-    private @NonNullByDefault({}) List<OSFileStore> fileStores;
-    private @NonNullByDefault({}) List<PowerSource> powerSources;
-    private @NonNullByDefault({}) List<HWDiskStore> 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<Integer, OSProcess> 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> 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> 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<Frequency> getCpuMaxFreq() {
-        long maxFreq = cpu.getMaxFreq();
-        return maxFreq >= 0 ? new QuantityType<>(maxFreq, Units.HERTZ) : null;
-    }
-
-    @Override
-    public @Nullable QuantityType<Frequency> getCpuFreq(int logicalProcessorIndex) {
-        long freq = cpu.getCurrentFreq()[logicalProcessorIndex];
-        return freq >= 0 ? new QuantityType<>(freq, Units.HERTZ) : null;
-    }
-
-    @Override
-    public QuantityType<DataAmount> getMemoryTotal() {
-        long totalMemory = memory.getTotal();
-        totalMemory = getSizeInMB(totalMemory);
-        return new QuantityType<>(totalMemory, Units.MEBIBYTE);
-    }
-
-    @Override
-    public QuantityType<DataAmount> getMemoryAvailable() {
-        long availableMemory = memory.getAvailable();
-        availableMemory = getSizeInMB(availableMemory);
-        return new QuantityType<>(availableMemory, Units.MEBIBYTE);
-    }
-
-    @Override
-    public QuantityType<DataAmount> 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<DataAmount> 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<DataAmount> 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<DataAmount> 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<Temperature> 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<ElectricPotential> 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<Time> getBatteryRemainingTime(int index) throws DeviceNotFoundException {
-        PowerSource powerSource = getDevice(powerSources, index);
-        powerSource.updateAttributes();
-        double remainingTimeInSeconds = powerSource.getTimeRemainingEstimated();
-        // The getTimeRemaining() method returns (-1.0) if is calculating or (-2.0) if the time is unlimited.
-        BigDecimal remainingTime = getTimeInMinutes(remainingTimeInSeconds);
-        return remainingTime.signum() == 1 ? new QuantityType<>(remainingTime, Units.MINUTE) : null;
-    }
-
-    @Override
-    public PercentType getBatteryRemainingCapacity(int index) throws DeviceNotFoundException {
-        PowerSource powerSource = getDevice(powerSources, index);
-        powerSource.updateAttributes();
-        double remainingCapacity = powerSource.getRemainingCapacityPercent();
-        BigDecimal remainingCapacityPercents = getPercentsValue(remainingCapacity);
-        return new PercentType(remainingCapacityPercents);
-    }
-
-    @Override
-    public StringType getBatteryName(int index) throws DeviceNotFoundException {
-        PowerSource powerSource = getDevice(powerSources, index);
-        String name = powerSource.getName();
-        return new StringType(name);
-    }
-
-    @Override
-    public @Nullable PercentType getMemoryAvailablePercent() {
-        long availableMemory = memory.getAvailable();
-        long totalMemory = memory.getTotal();
-        if (totalMemory > 0) {
-            double freePercentDecimal = (double) availableMemory / (double) totalMemory;
-            BigDecimal freePercent = getPercentsValue(freePercentDecimal);
-            return new PercentType(freePercent);
-        } else {
-            return null;
-        }
-    }
-
-    @Override
-    public @Nullable PercentType getMemoryUsedPercent() {
-        long availableMemory = memory.getAvailable();
-        long totalMemory = memory.getTotal();
-        long usedMemory = totalMemory - availableMemory;
-        if (totalMemory > 0) {
-            double usedPercentDecimal = (double) usedMemory / (double) totalMemory;
-            BigDecimal usedPercent = getPercentsValue(usedPercentDecimal);
-            return new PercentType(usedPercent);
-        } else {
-            return null;
-        }
-    }
-
-    @Override
-    public StringType getDriveName(int deviceIndex) throws DeviceNotFoundException {
-        HWDiskStore drive = getDevice(drives, deviceIndex);
-        String name = drive.getName();
-        return new StringType(name);
-    }
-
-    @Override
-    public StringType getDriveModel(int deviceIndex) throws DeviceNotFoundException {
-        HWDiskStore drive = getDevice(drives, deviceIndex);
-        String model = drive.getModel();
-        return new StringType(model);
-    }
-
-    @Override
-    public StringType getDriveSerialNumber(int deviceIndex) throws DeviceNotFoundException {
-        HWDiskStore drive = getDevice(drives, deviceIndex);
-        String serialNumber = drive.getSerial();
-        return new StringType(serialNumber);
-    }
-
-    @Override
-    public QuantityType<DataAmount> getSwapTotal() {
-        long swapTotal = memory.getVirtualMemory().getSwapTotal();
-        swapTotal = getSizeInMB(swapTotal);
-        return new QuantityType<>(swapTotal, Units.MEBIBYTE);
-    }
-
-    @Override
-    public QuantityType<DataAmount> getSwapAvailable() {
-        long swapTotal = memory.getVirtualMemory().getSwapTotal();
-        long swapUsed = memory.getVirtualMemory().getSwapUsed();
-        long swapAvailable = swapTotal - swapUsed;
-        swapAvailable = getSizeInMB(swapAvailable);
-        return new QuantityType<>(swapAvailable, Units.MEBIBYTE);
-    }
-
-    @Override
-    public QuantityType<DataAmount> getSwapUsed() {
-        long swapUsed = memory.getVirtualMemory().getSwapUsed();
-        swapUsed = getSizeInMB(swapUsed);
-        return new QuantityType<>(swapUsed, Units.MEBIBYTE);
-    }
-
-    @Override
-    public @Nullable PercentType getSwapAvailablePercent() {
-        long swapTotal = memory.getVirtualMemory().getSwapTotal();
-        long swapUsed = memory.getVirtualMemory().getSwapUsed();
-        long swapAvailable = swapTotal - swapUsed;
-        if (swapTotal > 0) {
-            double swapAvailablePercentDecimal = (double) swapAvailable / (double) swapTotal;
-            BigDecimal swapAvailablePercent = getPercentsValue(swapAvailablePercentDecimal);
-            return new PercentType(swapAvailablePercent);
-        } else {
-            return null;
-        }
-    }
-
-    @Override
-    public @Nullable PercentType getSwapUsedPercent() {
-        long swapTotal = memory.getVirtualMemory().getSwapTotal();
-        long swapUsed = memory.getVirtualMemory().getSwapUsed();
-        if (swapTotal > 0) {
-            double swapUsedPercentDecimal = (double) swapUsed / (double) swapTotal;
-            BigDecimal swapUsedPercent = getPercentsValue(swapUsedPercentDecimal);
-            return new PercentType(swapUsedPercent);
-        } else {
-            return null;
-        }
-    }
-
-    private long getSizeInMB(long sizeInBytes) {
-        return Math.round(sizeInBytes / (1024D * 1024));
-    }
-
-    private BigDecimal getPercentsValue(double decimalFraction) {
-        BigDecimal result = new BigDecimal(decimalFraction * 100);
-        result = result.setScale(PRECISION_AFTER_DECIMAL_SIGN, RoundingMode.HALF_UP);
-        return result;
-    }
-
-    private BigDecimal getTimeInMinutes(double timeInSeconds) {
-        BigDecimal timeInMinutes = new BigDecimal(timeInSeconds / 60);
-        timeInMinutes = timeInMinutes.setScale(PRECISION_AFTER_DECIMAL_SIGN, RoundingMode.UP);
-        return timeInMinutes;
-    }
-
-    @Override
-    public @Nullable PercentType getSystemCpuLoad() {
-        PercentType load = (ticks[0] > 0) ? new PercentType(getPercentsValue(cpu.getSystemCpuLoadBetweenTicks(ticks)))
-                : null;
-        ticks = cpu.getSystemCpuLoadTicks();
-        return load;
-    }
-
-    /**
-     * {@inheritDoc}
-     *
-     * This information is available only on Mac and Linux OS.
-     */
-    @Override
-    public @Nullable DecimalType getCpuLoad1() {
-        BigDecimal avarageCpuLoad = getAvarageCpuLoad(1);
-        return avarageCpuLoad.signum() == -1 ? null : new DecimalType(avarageCpuLoad);
-    }
-
-    /**
-     * {@inheritDoc}
-     *
-     * This information is available only on Mac and Linux OS.
-     */
-    @Override
-    public @Nullable DecimalType getCpuLoad5() {
-        BigDecimal avarageCpuLoad = getAvarageCpuLoad(5);
-        return avarageCpuLoad.signum() == -1 ? null : new DecimalType(avarageCpuLoad);
-    }
-
-    /**
-     * {@inheritDoc}
-     *
-     * This information is available only on Mac and Linux OS.
-     */
-    @Override
-    public @Nullable DecimalType getCpuLoad15() {
-        BigDecimal avarageCpuLoad = getAvarageCpuLoad(15);
-        return avarageCpuLoad.signum() == -1 ? null : new DecimalType(avarageCpuLoad);
-    }
-
-    private BigDecimal getAvarageCpuLoad(int timeInMinutes) {
-        // This parameter is specified in OSHI Javadoc
-        int index;
-        switch (timeInMinutes) {
-            case 1:
-                index = 0;
-                break;
-            case 5:
-                index = 1;
-                break;
-            case 15:
-                index = 2;
-                break;
-            default:
-                index = 2;
-        }
-        double processorLoads[] = cpu.getSystemLoadAverage(index + 1);
-        BigDecimal result = new BigDecimal(processorLoads[index]);
-        result = result.setScale(PRECISION_AFTER_DECIMAL_SIGN, RoundingMode.HALF_UP);
-        return result;
-    }
-
-    @Override
-    public QuantityType<Time> getCpuUptime() {
-        long seconds = operatingSystem.getSystemUptime();
-        return new QuantityType<>(getTimeInMinutes(seconds), Units.MINUTE);
-    }
-
-    @Override
-    public DecimalType getCpuThreads() {
-        int threadCount = operatingSystem.getThreadCount();
-        return new DecimalType(threadCount);
-    }
-
-    @Override
-    public StringType getNetworkMac(int networkIndex) throws DeviceNotFoundException {
-        NetworkIF network = getDevice(networks, networkIndex);
-        String mac = network.getMacaddr();
-        return new StringType(mac);
-    }
-
-    @Override
-    public DecimalType getNetworkPacketsReceived(int networkIndex) throws DeviceNotFoundException {
-        NetworkIF network = getDevice(networks, networkIndex);
-        network.updateAttributes();
-        long packRecv = network.getPacketsRecv();
-        return new DecimalType(packRecv);
-    }
-
-    @Override
-    public DecimalType getNetworkPacketsSent(int networkIndex) throws DeviceNotFoundException {
-        NetworkIF network = getDevice(networks, networkIndex);
-        network.updateAttributes();
-        long packSent = network.getPacketsSent();
-        return new DecimalType(packSent);
-    }
-
-    @Override
-    public QuantityType<DataAmount> getNetworkDataSent(int networkIndex) throws DeviceNotFoundException {
-        NetworkIF network = getDevice(networks, networkIndex);
-        network.updateAttributes();
-        long bytesSent = network.getBytesSent();
-        return new QuantityType<>(getSizeInMB(bytesSent), Units.MEBIBYTE);
-    }
-
-    @Override
-    public QuantityType<DataAmount> getNetworkDataReceived(int networkIndex) throws DeviceNotFoundException {
-        NetworkIF network = getDevice(networks, networkIndex);
-        network.updateAttributes();
-        long bytesRecv = network.getBytesRecv();
-        return new QuantityType<>(getSizeInMB(bytesRecv), Units.MEBIBYTE);
-    }
-
-    @Override
-    public int getCurrentProcessID() {
-        return operatingSystem.getProcessId();
-    }
-
-    @Override
-    public @Nullable StringType getProcessName(int pid) throws DeviceNotFoundException {
-        if (pid > 0) {
-            OSProcess process = getProcess(pid);
-            String name = process.getName();
-            return new StringType(name);
-        } else {
-            return null;
-        }
-    }
-
-    @Override
-    public @Nullable DecimalType getProcessCpuUsage(int pid) throws DeviceNotFoundException {
-        if (pid > 0) {
-            OSProcess process = getProcess(pid);
-            DecimalType load = (processTicks.containsKey(pid))
-                    ? new DecimalType(getPercentsValue(process.getProcessCpuLoadBetweenTicks(processTicks.get(pid))))
-                    : null;
-            processTicks.put(pid, process);
-            return load;
-        } else {
-            return null;
-        }
-    }
-
-    @Override
-    public @Nullable QuantityType<DataAmount> getProcessMemoryUsage(int pid) throws DeviceNotFoundException {
-        if (pid > 0) {
-            OSProcess process = getProcess(pid);
-            long memortInBytes = process.getResidentSetSize();
-            long memoryInMB = getSizeInMB(memortInBytes);
-            return new QuantityType<>(memoryInMB, Units.MEBIBYTE);
-        } else {
-            return null;
-        }
-    }
-
-    @Override
-    public @Nullable StringType getProcessPath(int pid) throws DeviceNotFoundException {
-        if (pid > 0) {
-            OSProcess process = getProcess(pid);
-            String path = process.getPath();
-            return new StringType(path);
-        } else {
-            return null;
-        }
-    }
-
-    @Override
-    public @Nullable DecimalType getProcessThreads(int pid) throws DeviceNotFoundException {
-        if (pid > 0) {
-            OSProcess process = getProcess(pid);
-            int threadCount = process.getThreadCount();
-            return new DecimalType(threadCount);
-        } else {
-            return null;
-        }
-    }
-
-    @Override
-    public int getNetworkIFCount() {
-        return networks.size();
-    }
-
-    @Override
-    public int getDisplayCount() {
-        return displays.size();
-    }
-
-    @Override
-    public int getFileOSStoreCount() {
-        return fileStores.size();
-    }
-
-    @Override
-    public int getPowerSourceCount() {
-        return powerSources.size();
-    }
-
-    @Override
-    public int getDriveCount() {
-        return drives.size();
-    }
-
-    @Override
-    public int getFanCount() {
-        return sensors.getFanSpeeds().length;
-    }
-}
diff --git a/bundles/org.openhab.binding.systeminfo/src/main/java/org/openhab/binding/systeminfo/internal/model/SystemInfoInterface.java b/bundles/org.openhab.binding.systeminfo/src/main/java/org/openhab/binding/systeminfo/internal/model/SystemInfoInterface.java
new file mode 100644 (file)
index 0000000..4f4ebea
--- /dev/null
@@ -0,0 +1,538 @@
+/**
+ * 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 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;
+
+/**
+ * {@link SystemInfoInterface} defines the methods needed to provide this binding with the required system information.
+ *
+ * @author Svilen Valkanov - Initial contribution
+ * @author Wouter Born - Add null annotations
+ * @author Mark Herwege - Add dynamic creation of extra channels
+ * @author Mark Herwege - Use units of measure
+ * @author Mark Herwege - Processor frequency channels
+ */
+@NonNullByDefault
+public interface SystemInfoInterface {
+
+    /**
+     * Initialize logic for the SystemInfo implementation
+     */
+    void initializeSystemInfo();
+
+    // Operating system info
+    /**
+     * Get the Family of the operating system /e.g. Windows, Unix,.../
+     */
+    StringType getOsFamily();
+
+    /**
+     * Get the manufacturer of the operating system
+     */
+    StringType getOsManufacturer();
+
+    /**
+     * Get the version of the operating system
+     *
+     * @return
+     */
+    StringType getOsVersion();
+
+    // CPU info
+    /**
+     * Get the name of the CPU
+     */
+    StringType getCpuName();
+
+    /**
+     * Get description about the CPU e.g (model, family, vendor, serial number, identifier, architecture(32bit or
+     * 64bit))
+     */
+    StringType getCpuDescription();
+
+    /**
+     * Get the number of logical CPUs/cores available for processing.
+     */
+    DecimalType getCpuLogicalCores();
+
+    /**
+     * Get the number of physical CPUs/cores available for processing.
+     */
+    DecimalType getCpuPhysicalCores();
+
+    /**
+     * Get the maximum CPU frequency of the processor.
+     */
+    @Nullable
+    QuantityType<Frequency> getCpuMaxFreq();
+
+    /**
+     * Get the current CPU frequency of a logical processor.
+     */
+    @Nullable
+    QuantityType<Frequency> getCpuFreq(int logicalProcessorIndex);
+
+    /**
+     * Returns the system cpu load.
+     *
+     * @return the system cpu load between 0 and 100% or null, if no information is available
+     */
+    @Nullable
+    PercentType getSystemCpuLoad();
+
+    /**
+     * Returns the system load average for the last minute.
+     *
+     * @return the load as a number of processes or null, if no information is available
+     */
+    @Nullable
+    DecimalType getCpuLoad1();
+
+    /**
+     * Returns the system load average for the last 5 minutes.
+     *
+     * @return the load as number of processes or null, if no information is available
+     */
+    @Nullable
+    DecimalType getCpuLoad5();
+
+    /**
+     * Returns the system load average for the last 15 minutes.
+     *
+     * @return the load as number of processes or null, if no information is available
+     */
+    @Nullable
+    DecimalType getCpuLoad15();
+
+    /**
+     * Get the System uptime (time since boot).
+     *
+     * @return time since boot
+     */
+    QuantityType<Time> getCpuUptime();
+
+    /**
+     * Get the number of threads currently running
+     *
+     * @return number of threads
+     */
+    DecimalType getCpuThreads();
+
+    // Memory info
+    /**
+     * Returns total size of memory
+     *
+     * @return memory size
+     */
+    QuantityType<DataAmount> getMemoryTotal();
+
+    /**
+     * Returns available size of memory
+     *
+     * @return memory size
+     */
+    QuantityType<DataAmount> getMemoryAvailable();
+
+    /**
+     * Returns used size of memory
+     *
+     * @return memory size
+     */
+    QuantityType<DataAmount> getMemoryUsed();
+
+    /**
+     * Percents of available memory on the machine
+     *
+     * @return percent of available memory or null, if no information is available
+     */
+    @Nullable
+    PercentType getMemoryAvailablePercent();
+
+    /**
+     * Percents of used memory on the machine
+     *
+     * @return percent of used memory or null, if no information is available
+     */
+    @Nullable
+    PercentType getMemoryUsedPercent();
+
+    // Swap memory info
+    /**
+     * Returns total size of swap memory
+     *
+     * @return memory size or 0, if there is no swap memory
+     */
+    QuantityType<DataAmount> getSwapTotal();
+
+    /**
+     * Returns available size swap of memory
+     *
+     * @return memory size or 0, if no there is no swap memory
+     */
+    QuantityType<DataAmount> getSwapAvailable();
+
+    /**
+     * Returns used size of swap memory
+     *
+     * @return memory size or 0, if no there is no swap memory
+     */
+    QuantityType<DataAmount> getSwapUsed();
+
+    /**
+     * Percents of available swap memory on the machine
+     *
+     * @return percent of available memory or null, if no there is no swap memory
+     */
+    @Nullable
+    PercentType getSwapAvailablePercent();
+
+    /**
+     * Percents of used swap memory on the machine
+     *
+     * @return percent of used memory or null, if no there is no swap memory
+     */
+    @Nullable
+    PercentType getSwapUsedPercent();
+
+    // Storage info
+    /**
+     * Returns the total space of the logical storage volume.
+     *
+     * @param deviceIndex - the index of the logical volume
+     * @return storage size
+     * @throws DeviceNotFoundException
+     */
+    QuantityType<DataAmount> getStorageTotal(int deviceIndex) throws DeviceNotFoundException;
+
+    /**
+     * Returns the available storage space on the logical storage volume
+     *
+     * @param deviceIndex - the index of the logical volume
+     * @return storage size
+     * @throws DeviceNotFoundException
+     */
+    QuantityType<DataAmount> getStorageAvailable(int deviceIndex) throws DeviceNotFoundException;
+
+    /**
+     * Gets the used storage space on the logical storage volume
+     *
+     * @param deviceIndex - the index of the logical volume
+     * @return storage size
+     * @throws DeviceNotFoundException
+     */
+    QuantityType<DataAmount> getStorageUsed(int deviceIndex) throws DeviceNotFoundException;
+
+    /**
+     * Gets the percent of available storage on the logical volume
+     *
+     * @param deviceIndex - the index of the logical volume
+     * @return percent of available storage or null
+     * @throws DeviceNotFoundException
+     */
+    @Nullable
+    PercentType getStorageAvailablePercent(int deviceIndex) throws DeviceNotFoundException;
+
+    /**
+     * Gets the percent of used storage on the logical volume
+     *
+     * @param deviceIndex - the index of the logical volume
+     * @return percent of used storage or null
+     * @throws DeviceNotFoundException
+     */
+    @Nullable
+    PercentType getStorageUsedPercent(int deviceIndex) throws DeviceNotFoundException;
+
+    /**
+     * Gets the name of the logical storage volume
+     *
+     * @throws DeviceNotFoundException
+     */
+    StringType getStorageName(int deviceIndex) throws DeviceNotFoundException;
+
+    /**
+     * Gets the type of the logical storage volume (e.g. NTFS, FAT32)
+     *
+     * @throws DeviceNotFoundException
+     */
+    StringType getStorageType(int deviceIndex) throws DeviceNotFoundException;
+
+    /**
+     * Gets the description of the logical storage volume
+     *
+     * @throws DeviceNotFoundException
+     */
+    StringType getStorageDescription(int deviceIndex) throws DeviceNotFoundException;
+
+    // Hardware drive info
+    /**
+     * Gets the name of the physical storage drive
+     *
+     * @param deviceIndex - index of the storage drive
+     * @throws DeviceNotFoundException
+     */
+    StringType getDriveName(int deviceIndex) throws DeviceNotFoundException;
+
+    /**
+     * Gets the model of the physical storage drive
+     *
+     * @param deviceIndex - index of the storage drive
+     * @throws DeviceNotFoundException
+     */
+    StringType getDriveModel(int deviceIndex) throws DeviceNotFoundException;
+
+    /**
+     * Gets the serial number of the physical storage drive
+     *
+     * @param deviceIndex - index of the storage drive
+     * @throws DeviceNotFoundException
+     */
+    StringType getDriveSerialNumber(int deviceIndex) throws DeviceNotFoundException;
+
+    // Network info
+    /**
+     * Get the Host IP address of the network.
+     *
+     * @param networkIndex - the index of the network
+     * @return 32-bit IPv4 address
+     * @throws DeviceNotFoundException
+     */
+    StringType getNetworkIp(int networkIndex) throws DeviceNotFoundException;
+
+    /**
+     * Get the name of this network.
+     *
+     * @param networkIndex - the index of the network
+     * @throws DeviceNotFoundException
+     */
+    StringType getNetworkName(int networkIndex) throws DeviceNotFoundException;
+
+    /**
+     * The description of the network. On some platforms, this is identical to the name.
+     *
+     * @param networkIndex - the index of the network
+     * @throws DeviceNotFoundException
+     */
+    StringType getNetworkDisplayName(int networkIndex) throws DeviceNotFoundException;
+
+    /**
+     * Gets the MAC Address of the network.
+     *
+     * @param networkIndex - the index of the network
+     * @throws DeviceNotFoundException
+     */
+    StringType getNetworkMac(int networkIndex) throws DeviceNotFoundException;
+
+    /**
+     * Get number of packets received
+     *
+     * @param networkIndex - the index of the network
+     * @throws DeviceNotFoundException
+     */
+    DecimalType getNetworkPacketsReceived(int networkIndex) throws DeviceNotFoundException;
+
+    /**
+     * Get number of packets sent
+     *
+     * @param networkIndex - the index of the network
+     * @throws DeviceNotFoundException
+     */
+    DecimalType getNetworkPacketsSent(int networkIndex) throws DeviceNotFoundException;
+
+    /**
+     * Get data sent for this network
+     *
+     * @param networkIndex - the index of the network
+     * @throws DeviceNotFoundException
+     */
+    QuantityType<DataAmount> getNetworkDataSent(int networkIndex) throws DeviceNotFoundException;
+
+    /**
+     * Get data received for this network
+     *
+     * @param networkIndex - the index of the network
+     * @throws DeviceNotFoundException
+     */
+    QuantityType<DataAmount> getNetworkDataReceived(int networkIndex) throws DeviceNotFoundException;
+
+    // Display info
+    /**
+     * Get information about the display device as product number, manufacturer, serial number, width and height in cm";
+     *
+     * @param deviceIndex - the index of the display device
+     * @throws DeviceNotFoundException
+     */
+    StringType getDisplayInformation(int deviceIndex) throws DeviceNotFoundException;
+
+    // Sensors info
+    /**
+     * Get the information from the CPU temperature sensors.
+     *
+     * @return Temperature if available, null otherwise.
+     */
+    @Nullable
+    QuantityType<Temperature> getSensorsCpuTemperature();
+
+    /**
+     * Get the information for the CPU voltage.
+     *
+     * @return Voltage if available, null otherwise.
+     */
+    @Nullable
+    QuantityType<ElectricPotential> getSensorsCpuVoltage();
+
+    /**
+     * Get fan speed
+     *
+     * @param deviceIndex
+     * @return Speed in rpm or null if unable to measure fan speed
+     * @throws DeviceNotFoundException
+     */
+    @Nullable
+    DecimalType getSensorsFanSpeed(int deviceIndex) throws DeviceNotFoundException;
+
+    // Battery info
+    /**
+     * Get estimated time remaining for the power source.
+     *
+     * @param deviceIndex
+     * @return duration remaining charge or null, if the time is estimated as unlimited
+     * @throws DeviceNotFoundException
+     */
+    @Nullable
+    QuantityType<Time> getBatteryRemainingTime(int deviceIndex) throws DeviceNotFoundException;
+
+    /**
+     * Battery remaining capacity.
+     *
+     * @param deviceIndex
+     * @return percentage value
+     * @throws DeviceNotFoundException
+     */
+    PercentType getBatteryRemainingCapacity(int deviceIndex) throws DeviceNotFoundException;
+
+    /**
+     * Get battery name
+     *
+     * @param deviceIndex
+     * @throws DeviceNotFoundException
+     */
+    StringType getBatteryName(int deviceIndex) throws DeviceNotFoundException;
+
+    /**
+     * Get PID of process executing this code
+     *
+     * @return current process ID
+     */
+    int getCurrentProcessID();
+
+    /**
+     * Returns the name of the process
+     *
+     * @param pid - the PID of the process
+     * @throws DeviceNotFoundException - thrown if process with this PID can not be found
+     */
+    @Nullable
+    StringType getProcessName(int pid) throws DeviceNotFoundException;
+
+    /**
+     * Returns the CPU usage of the process
+     *
+     * @param pid - the PID of the process
+     * @return - percentage value, can be above 100% if process uses multiple cores
+     * @throws DeviceNotFoundException - thrown if process with this PID can not be found
+     */
+    @Nullable
+    DecimalType getProcessCpuUsage(int pid) throws DeviceNotFoundException;
+
+    /**
+     * Returns the size of RAM memory only usage of the process
+     *
+     * @param pid - the PID of the process
+     * @return memory size
+     * @throws DeviceNotFoundException thrown if process with this PID can not be found
+     */
+    @Nullable
+    QuantityType<DataAmount> getProcessMemoryUsage(int pid) throws DeviceNotFoundException;
+
+    /**
+     * Returns the full path of the executing process.
+     *
+     * @param pid - the PID of the process
+     * @throws DeviceNotFoundException - thrown if process with this PID can not be found
+     */
+    @Nullable
+    StringType getProcessPath(int pid) throws DeviceNotFoundException;
+
+    /**
+     * Returns the number of threads in this process.
+     *
+     * @param pid - the PID of the process
+     * @throws DeviceNotFoundException - thrown if process with this PID can not be found
+     */
+    @Nullable
+    DecimalType getProcessThreads(int pid) throws DeviceNotFoundException;
+
+    /**
+     * Returns the number of network interfaces.
+     *
+     * @return network interface count
+     */
+    int getNetworkIFCount();
+
+    /**
+     * Returns the number of displays.
+     *
+     * @return display count
+     */
+    int getDisplayCount();
+
+    /**
+     * Returns the number of storages.
+     *
+     * @return storage count
+     */
+    int getFileOSStoreCount();
+
+    /**
+     * Returns the number of power sources/batteries.
+     *
+     * @return power source count
+     */
+    int getPowerSourceCount();
+
+    /**
+     * Returns the number of drives.
+     *
+     * @return drive count
+     */
+    int getDriveCount();
+
+    /**
+     * Returns the number of fans.
+     *
+     * @return fan count
+     */
+    int getFanCount();
+}
diff --git a/bundles/org.openhab.binding.systeminfo/src/main/java/org/openhab/binding/systeminfo/internal/model/SysteminfoInterface.java b/bundles/org.openhab.binding.systeminfo/src/main/java/org/openhab/binding/systeminfo/internal/model/SysteminfoInterface.java
deleted file mode 100644 (file)
index 8873ee3..0000000
+++ /dev/null
@@ -1,538 +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.model;
-
-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;
-
-/**
- * {@link SysteminfoInterface} defines the methods needed to provide this binding with the required system information.
- *
- * @author Svilen Valkanov - Initial contribution
- * @author Wouter Born - Add null annotations
- * @author Mark Herwege - Add dynamic creation of extra channels
- * @author Mark Herwege - Use units of measure
- * @author Mark Herwege - Processor frequency channels
- */
-@NonNullByDefault
-public interface SysteminfoInterface {
-
-    /**
-     * Initialize logic for the Systeminfo implementation
-     */
-    void initializeSysteminfo();
-
-    // Operating system info
-    /**
-     * Get the Family of the operating system /e.g. Windows, Unix,.../
-     */
-    StringType getOsFamily();
-
-    /**
-     * Get the manufacturer of the operating system
-     */
-    StringType getOsManufacturer();
-
-    /**
-     * Get the version of the operating system
-     *
-     * @return
-     */
-    StringType getOsVersion();
-
-    // CPU info
-    /**
-     * Get the name of the CPU
-     */
-    StringType getCpuName();
-
-    /**
-     * Get description about the CPU e.g (model, family, vendor, serial number, identifier, architecture(32bit or
-     * 64bit))
-     */
-    StringType getCpuDescription();
-
-    /**
-     * Get the number of logical CPUs/cores available for processing.
-     */
-    DecimalType getCpuLogicalCores();
-
-    /**
-     * Get the number of physical CPUs/cores available for processing.
-     */
-    DecimalType getCpuPhysicalCores();
-
-    /**
-     * Get the maximum CPU frequency of the processor.
-     */
-    @Nullable
-    QuantityType<Frequency> getCpuMaxFreq();
-
-    /**
-     * Get the current CPU frequency of a logical processor.
-     */
-    @Nullable
-    QuantityType<Frequency> getCpuFreq(int logicalProcessorIndex);
-
-    /**
-     * Returns the system cpu load.
-     *
-     * @return the system cpu load between 0 and 100% or null, if no information is available
-     */
-    @Nullable
-    PercentType getSystemCpuLoad();
-
-    /**
-     * Returns the system load average for the last minute.
-     *
-     * @return the load as a number of processes or null, if no information is available
-     */
-    @Nullable
-    DecimalType getCpuLoad1();
-
-    /**
-     * Returns the system load average for the last 5 minutes.
-     *
-     * @return the load as number of processes or null, if no information is available
-     */
-    @Nullable
-    DecimalType getCpuLoad5();
-
-    /**
-     * Returns the system load average for the last 15 minutes.
-     *
-     * @return the load as number of processes or null, if no information is available
-     */
-    @Nullable
-    DecimalType getCpuLoad15();
-
-    /**
-     * Get the System uptime (time since boot).
-     *
-     * @return time since boot
-     */
-    QuantityType<Time> getCpuUptime();
-
-    /**
-     * Get the number of threads currently running
-     *
-     * @return number of threads
-     */
-    DecimalType getCpuThreads();
-
-    // Memory info
-    /**
-     * Returns total size of memory
-     *
-     * @return memory size
-     */
-    QuantityType<DataAmount> getMemoryTotal();
-
-    /**
-     * Returns available size of memory
-     *
-     * @return memory size
-     */
-    QuantityType<DataAmount> getMemoryAvailable();
-
-    /**
-     * Returns used size of memory
-     *
-     * @return memory size
-     */
-    QuantityType<DataAmount> getMemoryUsed();
-
-    /**
-     * Percents of available memory on the machine
-     *
-     * @return percent of available memory or null, if no information is available
-     */
-    @Nullable
-    PercentType getMemoryAvailablePercent();
-
-    /**
-     * Percents of used memory on the machine
-     *
-     * @return percent of used memory or null, if no information is available
-     */
-    @Nullable
-    PercentType getMemoryUsedPercent();
-
-    // Swap memory info
-    /**
-     * Returns total size of swap memory
-     *
-     * @return memory size or 0, if there is no swap memory
-     */
-    QuantityType<DataAmount> getSwapTotal();
-
-    /**
-     * Returns available size swap of memory
-     *
-     * @return memory size or 0, if no there is no swap memory
-     */
-    QuantityType<DataAmount> getSwapAvailable();
-
-    /**
-     * Returns used size of swap memory
-     *
-     * @return memory size or 0, if no there is no swap memory
-     */
-    QuantityType<DataAmount> getSwapUsed();
-
-    /**
-     * Percents of available swap memory on the machine
-     *
-     * @return percent of available memory or null, if no there is no swap memory
-     */
-    @Nullable
-    PercentType getSwapAvailablePercent();
-
-    /**
-     * Percents of used swap memory on the machine
-     *
-     * @return percent of used memory or null, if no there is no swap memory
-     */
-    @Nullable
-    PercentType getSwapUsedPercent();
-
-    // Storage info
-    /**
-     * Returns the total space of the logical storage volume.
-     *
-     * @param deviceIndex - the index of the logical volume
-     * @return storage size
-     * @throws DeviceNotFoundException
-     */
-    QuantityType<DataAmount> getStorageTotal(int deviceIndex) throws DeviceNotFoundException;
-
-    /**
-     * Returns the available storage space on the logical storage volume
-     *
-     * @param deviceIndex - the index of the logical volume
-     * @return storage size
-     * @throws DeviceNotFoundException
-     */
-    QuantityType<DataAmount> getStorageAvailable(int deviceIndex) throws DeviceNotFoundException;
-
-    /**
-     * Gets the used storage space on the logical storage volume
-     *
-     * @param deviceIndex - the index of the logical volume
-     * @return storage size
-     * @throws DeviceNotFoundException
-     */
-    QuantityType<DataAmount> getStorageUsed(int deviceIndex) throws DeviceNotFoundException;
-
-    /**
-     * Gets the percent of available storage on the logical volume
-     *
-     * @param deviceIndex - the index of the logical volume
-     * @return percent of available storage or null
-     * @throws DeviceNotFoundException
-     */
-    @Nullable
-    PercentType getStorageAvailablePercent(int deviceIndex) throws DeviceNotFoundException;
-
-    /**
-     * Gets the percent of used storage on the logical volume
-     *
-     * @param deviceIndex - the index of the logical volume
-     * @return percent of used storage or null
-     * @throws DeviceNotFoundException
-     */
-    @Nullable
-    PercentType getStorageUsedPercent(int deviceIndex) throws DeviceNotFoundException;
-
-    /**
-     * Gets the name of the logical storage volume
-     *
-     * @throws DeviceNotFoundException
-     */
-    StringType getStorageName(int deviceIndex) throws DeviceNotFoundException;
-
-    /**
-     * Gets the type of the logical storage volume (e.g. NTFS, FAT32)
-     *
-     * @throws DeviceNotFoundException
-     */
-    StringType getStorageType(int deviceIndex) throws DeviceNotFoundException;
-
-    /**
-     * Gets the description of the logical storage volume
-     *
-     * @throws DeviceNotFoundException
-     */
-    StringType getStorageDescription(int deviceIndex) throws DeviceNotFoundException;
-
-    // Hardware drive info
-    /**
-     * Gets the name of the physical storage drive
-     *
-     * @param deviceIndex - index of the storage drive
-     * @throws DeviceNotFoundException
-     */
-    StringType getDriveName(int deviceIndex) throws DeviceNotFoundException;
-
-    /**
-     * Gets the model of the physical storage drive
-     *
-     * @param deviceIndex - index of the storage drive
-     * @throws DeviceNotFoundException
-     */
-    StringType getDriveModel(int deviceIndex) throws DeviceNotFoundException;
-
-    /**
-     * Gets the serial number of the physical storage drive
-     *
-     * @param deviceIndex - index of the storage drive
-     * @throws DeviceNotFoundException
-     */
-    StringType getDriveSerialNumber(int deviceIndex) throws DeviceNotFoundException;
-
-    // Network info
-    /**
-     * Get the Host IP address of the network.
-     *
-     * @param networkIndex - the index of the network
-     * @return 32-bit IPv4 address
-     * @throws DeviceNotFoundException
-     */
-    StringType getNetworkIp(int networkIndex) throws DeviceNotFoundException;
-
-    /**
-     * Get the name of this network.
-     *
-     * @param networkIndex - the index of the network
-     * @throws DeviceNotFoundException
-     */
-    StringType getNetworkName(int networkIndex) throws DeviceNotFoundException;
-
-    /**
-     * The description of the network. On some platforms, this is identical to the name.
-     *
-     * @param networkIndex - the index of the network
-     * @throws DeviceNotFoundException
-     */
-    StringType getNetworkDisplayName(int networkIndex) throws DeviceNotFoundException;
-
-    /**
-     * Gets the MAC Address of the network.
-     *
-     * @param networkIndex - the index of the network
-     * @throws DeviceNotFoundException
-     */
-    StringType getNetworkMac(int networkIndex) throws DeviceNotFoundException;
-
-    /**
-     * Get number of packets received
-     *
-     * @param networkIndex - the index of the network
-     * @throws DeviceNotFoundException
-     */
-    DecimalType getNetworkPacketsReceived(int networkIndex) throws DeviceNotFoundException;
-
-    /**
-     * Get number of packets sent
-     *
-     * @param networkIndex - the index of the network
-     * @throws DeviceNotFoundException
-     */
-    DecimalType getNetworkPacketsSent(int networkIndex) throws DeviceNotFoundException;
-
-    /**
-     * Get data sent for this network
-     *
-     * @param networkIndex - the index of the network
-     * @throws DeviceNotFoundException
-     */
-    QuantityType<DataAmount> getNetworkDataSent(int networkIndex) throws DeviceNotFoundException;
-
-    /**
-     * Get data received for this network
-     *
-     * @param networkIndex - the index of the network
-     * @throws DeviceNotFoundException
-     */
-    QuantityType<DataAmount> getNetworkDataReceived(int networkIndex) throws DeviceNotFoundException;
-
-    // Display info
-    /**
-     * Get information about the display device as product number, manufacturer, serial number, width and height in cm";
-     *
-     * @param deviceIndex - the index of the display device
-     * @throws DeviceNotFoundException
-     */
-    StringType getDisplayInformation(int deviceIndex) throws DeviceNotFoundException;
-
-    // Sensors info
-    /**
-     * Get the information from the CPU temperature sensors.
-     *
-     * @return Temperature if available, null otherwise.
-     */
-    @Nullable
-    QuantityType<Temperature> getSensorsCpuTemperature();
-
-    /**
-     * Get the information for the CPU voltage.
-     *
-     * @return Voltage if available, null otherwise.
-     */
-    @Nullable
-    QuantityType<ElectricPotential> getSensorsCpuVoltage();
-
-    /**
-     * Get fan speed
-     *
-     * @param deviceIndex
-     * @return Speed in rpm or null if unable to measure fan speed
-     * @throws DeviceNotFoundException
-     */
-    @Nullable
-    DecimalType getSensorsFanSpeed(int deviceIndex) throws DeviceNotFoundException;
-
-    // Battery info
-    /**
-     * Get estimated time remaining for the power source.
-     *
-     * @param deviceIndex
-     * @return duration remaining charge or null, if the time is estimated as unlimited
-     * @throws DeviceNotFoundException
-     */
-    @Nullable
-    QuantityType<Time> getBatteryRemainingTime(int deviceIndex) throws DeviceNotFoundException;
-
-    /**
-     * Battery remaining capacity.
-     *
-     * @param deviceIndex
-     * @return percentage value
-     * @throws DeviceNotFoundException
-     */
-    PercentType getBatteryRemainingCapacity(int deviceIndex) throws DeviceNotFoundException;
-
-    /**
-     * Get battery name
-     *
-     * @param deviceIndex
-     * @throws DeviceNotFoundException
-     */
-    StringType getBatteryName(int deviceIndex) throws DeviceNotFoundException;
-
-    /**
-     * Get PID of process executing this code
-     *
-     * @return current process ID
-     */
-    int getCurrentProcessID();
-
-    /**
-     * Returns the name of the process
-     *
-     * @param pid - the PID of the process
-     * @throws DeviceNotFoundException - thrown if process with this PID can not be found
-     */
-    @Nullable
-    StringType getProcessName(int pid) throws DeviceNotFoundException;
-
-    /**
-     * Returns the CPU usage of the process
-     *
-     * @param pid - the PID of the process
-     * @return - percentage value, can be above 100% if process uses multiple cores
-     * @throws DeviceNotFoundException - thrown if process with this PID can not be found
-     */
-    @Nullable
-    DecimalType getProcessCpuUsage(int pid) throws DeviceNotFoundException;
-
-    /**
-     * Returns the size of RAM memory only usage of the process
-     *
-     * @param pid - the PID of the process
-     * @return memory size
-     * @throws DeviceNotFoundException thrown if process with this PID can not be found
-     */
-    @Nullable
-    QuantityType<DataAmount> getProcessMemoryUsage(int pid) throws DeviceNotFoundException;
-
-    /**
-     * Returns the full path of the executing process.
-     *
-     * @param pid - the PID of the process
-     * @throws DeviceNotFoundException - thrown if process with this PID can not be found
-     */
-    @Nullable
-    StringType getProcessPath(int pid) throws DeviceNotFoundException;
-
-    /**
-     * Returns the number of threads in this process.
-     *
-     * @param pid - the PID of the process
-     * @throws DeviceNotFoundException - thrown if process with this PID can not be found
-     */
-    @Nullable
-    DecimalType getProcessThreads(int pid) throws DeviceNotFoundException;
-
-    /**
-     * Returns the number of network interfaces.
-     *
-     * @return network interface count
-     */
-    int getNetworkIFCount();
-
-    /**
-     * Returns the number of displays.
-     *
-     * @return display count
-     */
-    int getDisplayCount();
-
-    /**
-     * Returns the number of storages.
-     *
-     * @return storage count
-     */
-    int getFileOSStoreCount();
-
-    /**
-     * Returns the number of power sources/batteries.
-     *
-     * @return power source count
-     */
-    int getPowerSourceCount();
-
-    /**
-     * Returns the number of drives.
-     *
-     * @return drive count
-     */
-    int getDriveCount();
-
-    /**
-     * Returns the number of fans.
-     *
-     * @return fan count
-     */
-    int getFanCount();
-}
index b4b418f4e1397c8e680cf07a4696ea98294350c2..9ae612e6dbbfaf6d05c08961a274c283c58a2097 100644 (file)
@@ -12,7 +12,7 @@
 
   <artifactId>org.openhab.binding.systeminfo.tests</artifactId>
 
-  <name>openHAB Add-ons :: Integration Tests :: Systeminfo Binding Tests</name>
+  <name>openHAB Add-ons :: Integration Tests :: SystemInfo Binding Tests</name>
 
   <dependencies>
     <dependency>
diff --git a/itests/org.openhab.binding.systeminfo.tests/src/main/java/org/openhab/binding/systeminfo/test/SystemInfoOSGiTest.java b/itests/org.openhab.binding.systeminfo.tests/src/main/java/org/openhab/binding/systeminfo/test/SystemInfoOSGiTest.java
new file mode 100644 (file)
index 0000000..012350f
--- /dev/null
@@ -0,0 +1,1173 @@
+/**
+ * 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.test;
+
+import static java.lang.Thread.sleep;
+import static org.hamcrest.CoreMatchers.*;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.mockito.Mockito.*;
+
+import java.math.BigDecimal;
+import java.net.UnknownHostException;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Map;
+
+import javax.measure.quantity.ElectricPotential;
+import javax.measure.quantity.Frequency;
+import javax.measure.quantity.Temperature;
+import javax.measure.quantity.Time;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.mockito.junit.jupiter.MockitoSettings;
+import org.mockito.quality.Strictness;
+import org.openhab.binding.systeminfo.internal.SystemInfoBindingConstants;
+import org.openhab.binding.systeminfo.internal.SystemInfoHandlerFactory;
+import org.openhab.binding.systeminfo.internal.discovery.SystemInfoDiscoveryService;
+import org.openhab.binding.systeminfo.internal.handler.SystemInfoHandler;
+import org.openhab.binding.systeminfo.internal.model.DeviceNotFoundException;
+import org.openhab.binding.systeminfo.internal.model.OSHISystemInfo;
+import org.openhab.binding.systeminfo.internal.model.SystemInfoInterface;
+import org.openhab.core.config.core.Configuration;
+import org.openhab.core.config.discovery.DiscoveryResult;
+import org.openhab.core.config.discovery.DiscoveryService;
+import org.openhab.core.config.discovery.inbox.Inbox;
+import org.openhab.core.config.discovery.inbox.InboxPredicates;
+import org.openhab.core.i18n.UnitProvider;
+import org.openhab.core.items.GenericItem;
+import org.openhab.core.items.ItemNotFoundException;
+import org.openhab.core.items.ItemRegistry;
+import org.openhab.core.library.dimension.DataAmount;
+import org.openhab.core.library.items.NumberItem;
+import org.openhab.core.library.items.StringItem;
+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.openhab.core.test.java.JavaOSGiTest;
+import org.openhab.core.test.storage.VolatileStorageService;
+import org.openhab.core.thing.Channel;
+import org.openhab.core.thing.ChannelUID;
+import org.openhab.core.thing.ManagedThingProvider;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.thing.ThingProvider;
+import org.openhab.core.thing.ThingRegistry;
+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.ThingHandler;
+import org.openhab.core.thing.binding.ThingHandlerFactory;
+import org.openhab.core.thing.binding.builder.ChannelBuilder;
+import org.openhab.core.thing.binding.builder.ThingBuilder;
+import org.openhab.core.thing.link.ItemChannelLink;
+import org.openhab.core.thing.link.ManagedItemChannelLinkProvider;
+import org.openhab.core.thing.type.ChannelKind;
+import org.openhab.core.thing.type.ChannelTypeUID;
+import org.openhab.core.types.State;
+import org.openhab.core.types.UnDefType;
+
+/**
+ * OSGi tests for the {@link SystemInfoHandler}
+ *
+ * @author Svilen Valkanov - Initial contribution
+ * @author Lyubomir Papazov - Created a mock systeminfo object. This way, access to the user's OS will not be required,
+ *         but mock data will be used instead, avoiding potential errors from the OS queries.
+ * @author Wouter Born - Migrate Groovy to Java tests
+ * @author Mark Herwege - Processor frequency channels
+ */
+@NonNullByDefault
+@ExtendWith(MockitoExtension.class)
+@MockitoSettings(strictness = Strictness.LENIENT)
+public class SystemInfoOSGiTest extends JavaOSGiTest {
+
+    private static final String DEFAULT_TEST_THING_NAME = "work";
+    private static final String DEFAULT_TEST_ITEM_NAME = "test";
+    private static final String DEFAULT_CHANNEL_TEST_PRIORITY = "High";
+    private static final int DEFAULT_CHANNEL_PID = -1;
+    private static final String DEFAULT_TEST_CHANNEL_ID = SystemInfoBindingConstants.CHANNEL_CPU_LOAD;
+    private static final int DEFAULT_DEVICE_INDEX = 0;
+
+    /**
+     * Refresh time in seconds for tasks with priority High.
+     * Default value for the parameter interval_high in the thing configuration
+     */
+    private static final int DEFAULT_TEST_INTERVAL_HIGH = 1;
+
+    /**
+     * Refresh time in seconds for tasks with priority Medium.
+     */
+    private static final int DEFAULT_TEST_INTERVAL_MEDIUM = 3;
+
+    private @Nullable Thing systeminfoThing;
+    private @Nullable GenericItem testItem;
+
+    private @Mock @NonNullByDefault({}) OSHISystemInfo mockedSystemInfo;
+    private @NonNullByDefault({}) SystemInfoHandlerFactory systeminfoHandlerFactory;
+    private @NonNullByDefault({}) ThingRegistry thingRegistry;
+    private @NonNullByDefault({}) ItemRegistry itemRegistry;
+    private @NonNullByDefault({}) ManagedThingProvider managedThingProvider;
+    private @NonNullByDefault({}) ManagedItemChannelLinkProvider itemChannelLinkProvider;
+    private @NonNullByDefault({}) UnitProvider unitProvider;
+    private @NonNullByDefault({}) VolatileStorageService volatileStorageService;
+
+    @BeforeEach
+    public void setUp() {
+        volatileStorageService = new VolatileStorageService();
+        registerService(volatileStorageService);
+
+        // Preparing the mock with OS properties, that are used in the initialize method of SystemInfoHandler
+        // Make this lenient because the assertInvalidThingConfigurationValuesAreHandled test does not require them
+        lenient().when(mockedSystemInfo.getCpuLogicalCores()).thenReturn(new DecimalType(1));
+        lenient().when(mockedSystemInfo.getCpuPhysicalCores()).thenReturn(new DecimalType(1));
+        lenient().when(mockedSystemInfo.getOsFamily()).thenReturn(new StringType("Mock OS"));
+        lenient().when(mockedSystemInfo.getOsManufacturer()).thenReturn(new StringType("Mock OS Manufacturer"));
+        lenient().when(mockedSystemInfo.getOsVersion()).thenReturn(new StringType("Mock Os Version"));
+        // Following mock method returns will make sure the thing does not get recreated with extra channels
+        lenient().when(mockedSystemInfo.getNetworkIFCount()).thenReturn(1);
+        lenient().when(mockedSystemInfo.getDisplayCount()).thenReturn(1);
+        lenient().when(mockedSystemInfo.getFileOSStoreCount()).thenReturn(1);
+        lenient().when(mockedSystemInfo.getPowerSourceCount()).thenReturn(1);
+        lenient().when(mockedSystemInfo.getDriveCount()).thenReturn(1);
+        lenient().when(mockedSystemInfo.getFanCount()).thenReturn(1);
+
+        registerService(mockedSystemInfo);
+
+        waitForAssert(() -> {
+            systeminfoHandlerFactory = getService(ThingHandlerFactory.class, SystemInfoHandlerFactory.class);
+            assertThat(systeminfoHandlerFactory, is(notNullValue()));
+        });
+
+        if (systeminfoHandlerFactory != null) {
+            // Unbind oshiSystemInfo service and bind the mock service to make the systeminfo binding tests independent
+            // of the external OSHI library
+            SystemInfoInterface oshiSystemInfo = getService(SystemInfoInterface.class);
+            if (oshiSystemInfo != null) {
+                systeminfoHandlerFactory.unbindSystemInfo(oshiSystemInfo);
+            }
+            systeminfoHandlerFactory.bindSystemInfo(mockedSystemInfo);
+        }
+
+        waitForAssert(() -> {
+            thingRegistry = getService(ThingRegistry.class);
+            assertThat(thingRegistry, is(notNullValue()));
+        });
+
+        waitForAssert(() -> {
+            itemRegistry = getService(ItemRegistry.class);
+            assertThat(itemRegistry, is(notNullValue()));
+        });
+
+        waitForAssert(() -> {
+            managedThingProvider = getService(ThingProvider.class, ManagedThingProvider.class);
+            assertThat(managedThingProvider, is(notNullValue()));
+        });
+
+        waitForAssert(() -> {
+            itemChannelLinkProvider = getService(ManagedItemChannelLinkProvider.class);
+            assertThat(itemChannelLinkProvider, is(notNullValue()));
+        });
+
+        waitForAssert(() -> {
+            unitProvider = getService(UnitProvider.class);
+            assertThat(unitProvider, is(notNullValue()));
+        });
+    }
+
+    @AfterEach
+    public void tearDown() {
+        Thing thing = systeminfoThing;
+        if (thing != null) {
+            // Remove the systeminfo thing. The handler will also be disposed automatically
+            Thing removedThing = thingRegistry.forceRemove(thing.getUID());
+            assertThat("The systeminfo thing cannot be deleted", removedThing, is(notNullValue()));
+            waitForAssert(() -> {
+                ThingHandler systemInfoHandler = thing.getHandler();
+                assertThat(systemInfoHandler, is(nullValue()));
+            });
+            managedThingProvider.remove(thing.getUID());
+        }
+
+        if (testItem != null) {
+            itemRegistry.remove(DEFAULT_TEST_ITEM_NAME);
+        }
+
+        unregisterService(mockedSystemInfo);
+        unregisterService(volatileStorageService);
+    }
+
+    private void initializeThingWithChannelAndPID(String channelID, String acceptedItemType, int pid) {
+        Configuration thingConfig = new Configuration();
+        thingConfig.put(SystemInfoBindingConstants.HIGH_PRIORITY_REFRESH_TIME,
+                new BigDecimal(DEFAULT_TEST_INTERVAL_HIGH));
+        thingConfig.put(SystemInfoBindingConstants.MEDIUM_PRIORITY_REFRESH_TIME,
+                new BigDecimal(DEFAULT_TEST_INTERVAL_MEDIUM));
+        String priority = DEFAULT_CHANNEL_TEST_PRIORITY;
+
+        initializeThing(thingConfig, channelID, acceptedItemType, priority, pid);
+    }
+
+    private void initializeThingWithChannelAndPriority(String channelID, String acceptedItemType, String priority) {
+        Configuration thingConfig = new Configuration();
+        thingConfig.put(SystemInfoBindingConstants.HIGH_PRIORITY_REFRESH_TIME,
+                new BigDecimal(DEFAULT_TEST_INTERVAL_HIGH));
+        thingConfig.put(SystemInfoBindingConstants.MEDIUM_PRIORITY_REFRESH_TIME,
+                new BigDecimal(DEFAULT_TEST_INTERVAL_MEDIUM));
+        int pid = DEFAULT_CHANNEL_PID;
+
+        initializeThing(thingConfig, channelID, acceptedItemType, priority, pid);
+    }
+
+    private void initializeThingWithConfiguration(Configuration config) {
+        String priority = DEFAULT_CHANNEL_TEST_PRIORITY;
+        String channelID = DEFAULT_TEST_CHANNEL_ID;
+        String acceptedItemType = "String";
+        int pid = DEFAULT_CHANNEL_PID;
+
+        initializeThing(config, channelID, acceptedItemType, priority, pid);
+    }
+
+    private void initializeThingWithChannel(String channelID, String acceptedItemType) {
+        Configuration thingConfig = new Configuration();
+        thingConfig.put(SystemInfoBindingConstants.HIGH_PRIORITY_REFRESH_TIME,
+                new BigDecimal(DEFAULT_TEST_INTERVAL_HIGH));
+        thingConfig.put(SystemInfoBindingConstants.MEDIUM_PRIORITY_REFRESH_TIME,
+                new BigDecimal(DEFAULT_TEST_INTERVAL_MEDIUM));
+
+        String priority = DEFAULT_CHANNEL_TEST_PRIORITY;
+        int pid = DEFAULT_CHANNEL_PID;
+        initializeThing(thingConfig, channelID, acceptedItemType, priority, pid);
+    }
+
+    private void initializeThing(Configuration thingConfiguration, String channelID, String acceptedItemType,
+            String priority, int pid) {
+        ThingTypeUID thingTypeUID = SystemInfoBindingConstants.THING_TYPE_COMPUTER;
+        ThingUID thingUID = new ThingUID(thingTypeUID, DEFAULT_TEST_THING_NAME);
+
+        ChannelUID channelUID = new ChannelUID(thingUID, channelID);
+        String channelTypeId = channelUID.getIdWithoutGroup();
+        if ("load1".equals(channelTypeId) || "load5".equals(channelTypeId) || "load15".equals(channelTypeId)) {
+            channelTypeId = "loadAverage";
+        }
+        ChannelTypeUID channelTypeUID = new ChannelTypeUID(SystemInfoBindingConstants.BINDING_ID, channelTypeId);
+        Configuration channelConfig = new Configuration();
+        channelConfig.put("priority", priority);
+        channelConfig.put("pid", new BigDecimal(pid));
+        Channel channel = ChannelBuilder.create(channelUID, acceptedItemType).withType(channelTypeUID)
+                .withKind(ChannelKind.STATE).withConfiguration(channelConfig).build();
+
+        ThingBuilder thingBuilder = ThingBuilder.create(thingTypeUID, thingUID).withConfiguration(thingConfiguration)
+                .withChannel(channel);
+        // Make sure the thingTypeVersion matches the highest version in the update instructions of the binding to avoid
+        // new channels being added and the thing not initializing
+        thingBuilder = thingBuilder.withProperties(Map.of("thingTypeVersion", "1"));
+        Thing thing = thingBuilder.build();
+        systeminfoThing = thing;
+
+        managedThingProvider.add(thing);
+
+        waitForAssert(() -> {
+            SystemInfoHandler handler = (SystemInfoHandler) thing.getHandler();
+            assertThat(handler, is(notNullValue()));
+        });
+
+        waitForAssert(() -> {
+            assertThat("Thing is not initialized, before an Item is created", thing.getStatus(),
+                    anyOf(equalTo(ThingStatus.OFFLINE), equalTo(ThingStatus.ONLINE)));
+        });
+
+        intializeItem(channelUID, DEFAULT_TEST_ITEM_NAME, acceptedItemType);
+    }
+
+    private void assertItemState(String acceptedItemType, String itemName, String priority, State expectedState) {
+        Thing thing = systeminfoThing;
+        if (thing == null) {
+            throw new AssertionError("Thing is null");
+        }
+        waitForAssert(() -> {
+            ThingStatusDetail thingStatusDetail = thing.getStatusInfo().getStatusDetail();
+            String description = thing.getStatusInfo().getDescription();
+            assertThat("Thing status detail is " + thingStatusDetail + " with description " + description,
+                    thing.getStatus(), is(equalTo(ThingStatus.ONLINE)));
+        });
+        // The binding starts all refresh tasks in SystemInfoHandler.scheduleUpdates() after this delay !
+        try {
+            sleep(SystemInfoHandler.WAIT_TIME_CHANNEL_ITEM_LINK_INIT * 1000);
+        } catch (InterruptedException e) {
+            throw new AssertionError("Interrupted while sleeping");
+        }
+
+        GenericItem item;
+        try {
+            item = (GenericItem) itemRegistry.getItem(itemName);
+        } catch (ItemNotFoundException e) {
+            throw new AssertionError("Item not found in registry");
+        }
+
+        int waitTime;
+        if ("High".equals(priority)) {
+            waitTime = DEFAULT_TEST_INTERVAL_HIGH * 1000;
+        } else if ("Medium".equals(priority)) {
+            waitTime = DEFAULT_TEST_INTERVAL_MEDIUM * 1000;
+        } else {
+            waitTime = 100;
+        }
+
+        waitForAssert(() -> {
+            State itemState = item.getState();
+            assertThat(itemState, is(equalTo(expectedState)));
+        }, waitTime, DFL_SLEEP_TIME);
+    }
+
+    private void intializeItem(ChannelUID channelUID, String itemName, String acceptedItemType) {
+        GenericItem item = null;
+        if (acceptedItemType.startsWith("Number")) {
+            item = new NumberItem(acceptedItemType, itemName, unitProvider);
+        } else if ("String".equals(acceptedItemType)) {
+            item = new StringItem(itemName);
+        }
+        if (item == null) {
+            throw new AssertionError("Item is null");
+        }
+        itemRegistry.add(item);
+        testItem = item;
+
+        itemChannelLinkProvider.add(new ItemChannelLink(itemName, channelUID));
+    }
+
+    @Test
+    public void assertInvalidThingConfigurationValuesAreHandled() {
+        Configuration configuration = new Configuration();
+
+        // invalid value - must be positive
+        int refreshIntervalHigh = -5;
+        configuration.put(SystemInfoBindingConstants.HIGH_PRIORITY_REFRESH_TIME, new BigDecimal(refreshIntervalHigh));
+
+        int refreshIntervalMedium = 3;
+        configuration.put(SystemInfoBindingConstants.MEDIUM_PRIORITY_REFRESH_TIME,
+                new BigDecimal(refreshIntervalMedium));
+        initializeThingWithConfiguration(configuration);
+
+        testInvalidConfiguration();
+    }
+
+    private void testInvalidConfiguration() {
+        waitForAssert(() -> {
+            Thing thing = systeminfoThing;
+            if (thing != null) {
+                assertThat("Invalid configuration is used !", thing.getStatus(), is(equalTo(ThingStatus.OFFLINE)));
+                assertThat(thing.getStatusInfo().getStatusDetail(),
+                        is(equalTo(ThingStatusDetail.HANDLER_INITIALIZING_ERROR)));
+                assertThat(thing.getStatusInfo().getDescription(), is(equalTo("@text/offline.cannot-initialize")));
+            }
+        });
+    }
+
+    @Test
+    public void assertMediumPriorityChannelIsUpdated() {
+        String channnelID = DEFAULT_TEST_CHANNEL_ID;
+        String acceptedItemType = "Number";
+        String priority = "Medium";
+
+        initializeThingWithChannelAndPriority(channnelID, acceptedItemType, priority);
+        assertItemState(acceptedItemType, DEFAULT_TEST_ITEM_NAME, priority, UnDefType.UNDEF);
+    }
+
+    @Test
+    public void assertStateOfSecondDeviceIsUpdated() {
+        // This test assumes that at least 2 network interfaces are present on the test platform
+        int deviceIndex = 1;
+        String channnelID = "network" + deviceIndex + "#mac";
+        String acceptedItemType = "String";
+
+        initializeThingWithChannel(channnelID, acceptedItemType);
+        assertItemState(acceptedItemType, DEFAULT_TEST_ITEM_NAME, DEFAULT_CHANNEL_TEST_PRIORITY, UnDefType.UNDEF);
+    }
+
+    @Test
+    public void assertChannelCpuMaxFreq() {
+        String channnelID = SystemInfoBindingConstants.CHANNEL_CPU_MAXFREQ;
+        String acceptedItemType = "Number:Frequency";
+
+        QuantityType<Frequency> mockedCpuMaxFreqValue = new QuantityType<>(2500, Units.HERTZ);
+        when(mockedSystemInfo.getCpuMaxFreq()).thenReturn(mockedCpuMaxFreqValue);
+
+        initializeThingWithChannel(channnelID, acceptedItemType);
+        assertItemState(acceptedItemType, DEFAULT_TEST_ITEM_NAME, DEFAULT_CHANNEL_TEST_PRIORITY, mockedCpuMaxFreqValue);
+    }
+
+    @Test
+    public void assertChannelCpuFreq() {
+        String channnelID = SystemInfoBindingConstants.CHANNEL_CPU_FREQ;
+        String acceptedItemType = "Number:Frequency";
+
+        QuantityType<Frequency> mockedCpuFreqValue = new QuantityType<>(2500, Units.HERTZ);
+        when(mockedSystemInfo.getCpuFreq(0)).thenReturn(mockedCpuFreqValue);
+
+        initializeThingWithChannel(channnelID, acceptedItemType);
+        assertItemState(acceptedItemType, DEFAULT_TEST_ITEM_NAME, DEFAULT_CHANNEL_TEST_PRIORITY, mockedCpuFreqValue);
+    }
+
+    @Test
+    public void assertChannelCpuLoadIsUpdated() {
+        String channnelID = SystemInfoBindingConstants.CHANNEL_CPU_LOAD;
+        String acceptedItemType = "Number";
+
+        PercentType mockedCpuLoadValue = new PercentType(9);
+        when(mockedSystemInfo.getSystemCpuLoad()).thenReturn(mockedCpuLoadValue);
+
+        initializeThingWithChannel(channnelID, acceptedItemType);
+        assertItemState(acceptedItemType, DEFAULT_TEST_ITEM_NAME, DEFAULT_CHANNEL_TEST_PRIORITY, mockedCpuLoadValue);
+    }
+
+    @Test
+    public void assertChannelCpuLoad1IsUpdated() {
+        String channnelID = SystemInfoBindingConstants.CHANNEL_CPU_LOAD_1;
+        String acceptedItemType = "Number";
+
+        DecimalType mockedCpuLoad1Value = new DecimalType(1.1);
+        when(mockedSystemInfo.getCpuLoad1()).thenReturn(mockedCpuLoad1Value);
+
+        initializeThingWithChannel(channnelID, acceptedItemType);
+        assertItemState(acceptedItemType, DEFAULT_TEST_ITEM_NAME, DEFAULT_CHANNEL_TEST_PRIORITY, mockedCpuLoad1Value);
+    }
+
+    @Test
+    public void assertChannelCpuLoad5IsUpdated() {
+        String channnelID = SystemInfoBindingConstants.CHANNEL_CPU_LOAD_5;
+        String acceptedItemType = "Number";
+
+        DecimalType mockedCpuLoad5Value = new DecimalType(5.5);
+        when(mockedSystemInfo.getCpuLoad5()).thenReturn(mockedCpuLoad5Value);
+
+        initializeThingWithChannel(channnelID, acceptedItemType);
+        assertItemState(acceptedItemType, DEFAULT_TEST_ITEM_NAME, DEFAULT_CHANNEL_TEST_PRIORITY, mockedCpuLoad5Value);
+    }
+
+    @Test
+    public void assertChannelCpuLoad15IsUpdated() {
+        String channnelID = SystemInfoBindingConstants.CHANNEL_CPU_LOAD_15;
+        String acceptedItemType = "Number";
+
+        DecimalType mockedCpuLoad15Value = new DecimalType(15.15);
+        when(mockedSystemInfo.getCpuLoad15()).thenReturn(mockedCpuLoad15Value);
+
+        initializeThingWithChannel(channnelID, acceptedItemType);
+        assertItemState(acceptedItemType, DEFAULT_TEST_ITEM_NAME, DEFAULT_CHANNEL_TEST_PRIORITY, mockedCpuLoad15Value);
+    }
+
+    @Test
+    public void assertChannelCpuThreadsIsUpdated() {
+        String channnelID = SystemInfoBindingConstants.CHANNEL_CPU_THREADS;
+        String acceptedItemType = "Number";
+
+        DecimalType mockedCpuThreadsValue = new DecimalType(16);
+        when(mockedSystemInfo.getCpuThreads()).thenReturn(mockedCpuThreadsValue);
+
+        initializeThingWithChannel(channnelID, acceptedItemType);
+        assertItemState(acceptedItemType, DEFAULT_TEST_ITEM_NAME, DEFAULT_CHANNEL_TEST_PRIORITY, mockedCpuThreadsValue);
+    }
+
+    @Test
+    public void assertChannelCpuUptimeIsUpdated() {
+        String channnelID = SystemInfoBindingConstants.CHANNEL_CPU_UPTIME;
+        String acceptedItemType = "Number:Time";
+
+        QuantityType<Time> mockedCpuUptimeValue = new QuantityType<>(100, Units.MINUTE);
+        when(mockedSystemInfo.getCpuUptime()).thenReturn(mockedCpuUptimeValue);
+
+        initializeThingWithChannel(channnelID, acceptedItemType);
+        assertItemState(acceptedItemType, DEFAULT_TEST_ITEM_NAME, DEFAULT_CHANNEL_TEST_PRIORITY, mockedCpuUptimeValue);
+    }
+
+    @Test
+    public void assertChannelCpuDescriptionIsUpdated() {
+        String channnelID = SystemInfoBindingConstants.CHANNEL_CPU_DESCRIPTION;
+        String acceptedItemType = "String";
+
+        StringType mockedCpuDescriptionValue = new StringType("Mocked Cpu Descr");
+        when(mockedSystemInfo.getCpuDescription()).thenReturn(mockedCpuDescriptionValue);
+
+        initializeThingWithChannel(channnelID, acceptedItemType);
+        assertItemState(acceptedItemType, DEFAULT_TEST_ITEM_NAME, DEFAULT_CHANNEL_TEST_PRIORITY,
+                mockedCpuDescriptionValue);
+    }
+
+    @Test
+    public void assertChannelCpuNameIsUpdated() {
+        String channnelID = SystemInfoBindingConstants.CHANNEL_CPU_NAME;
+        String acceptedItemType = "String";
+
+        StringType mockedCpuNameValue = new StringType("Mocked Cpu Name");
+        when(mockedSystemInfo.getCpuName()).thenReturn(mockedCpuNameValue);
+
+        initializeThingWithChannel(channnelID, acceptedItemType);
+        assertItemState(acceptedItemType, DEFAULT_TEST_ITEM_NAME, DEFAULT_CHANNEL_TEST_PRIORITY, mockedCpuNameValue);
+    }
+
+    @Test
+    public void assertChannelMemoryAvailableIsUpdated() {
+        String channnelID = SystemInfoBindingConstants.CHANNEL_MEMORY_AVAILABLE;
+        String acceptedItemType = "Number:DataAmount";
+
+        QuantityType<DataAmount> mockedMemoryAvailableValue = new QuantityType<>(1000, Units.MEBIBYTE);
+        when(mockedSystemInfo.getMemoryAvailable()).thenReturn(mockedMemoryAvailableValue);
+
+        initializeThingWithChannel(channnelID, acceptedItemType);
+        assertItemState(acceptedItemType, DEFAULT_TEST_ITEM_NAME, DEFAULT_CHANNEL_TEST_PRIORITY,
+                mockedMemoryAvailableValue);
+    }
+
+    @Test
+    public void assertChannelMemoryUsedIsUpdated() {
+        String channnelID = SystemInfoBindingConstants.CHANNEL_MEMORY_USED;
+        String acceptedItemType = "Number:DataAmount";
+
+        QuantityType<DataAmount> mockedMemoryUsedValue = new QuantityType<>(24, Units.MEBIBYTE);
+        when(mockedSystemInfo.getMemoryUsed()).thenReturn(mockedMemoryUsedValue);
+
+        initializeThingWithChannel(channnelID, acceptedItemType);
+        assertItemState(acceptedItemType, DEFAULT_TEST_ITEM_NAME, DEFAULT_CHANNEL_TEST_PRIORITY, mockedMemoryUsedValue);
+    }
+
+    @Test
+    public void assertChannelMemoryTotalIsUpdated() {
+        String channnelID = SystemInfoBindingConstants.CHANNEL_MEMORY_TOTAL;
+        String acceptedItemType = "Number:DataAmount";
+
+        QuantityType<DataAmount> mockedMemoryTotalValue = new QuantityType<>(1024, Units.MEBIBYTE);
+        when(mockedSystemInfo.getMemoryTotal()).thenReturn(mockedMemoryTotalValue);
+
+        initializeThingWithChannel(channnelID, acceptedItemType);
+        assertItemState(acceptedItemType, DEFAULT_TEST_ITEM_NAME, DEFAULT_CHANNEL_TEST_PRIORITY,
+                mockedMemoryTotalValue);
+    }
+
+    @Test
+    public void assertChannelMemoryAvailablePercentIsUpdated() {
+        String channnelID = SystemInfoBindingConstants.CHANNEL_MEMORY_AVAILABLE_PERCENT;
+        String acceptedItemType = "Number";
+
+        PercentType mockedMemoryAvailablePercentValue = new PercentType(97);
+        when(mockedSystemInfo.getMemoryAvailablePercent()).thenReturn(mockedMemoryAvailablePercentValue);
+
+        initializeThingWithChannel(channnelID, acceptedItemType);
+        assertItemState(acceptedItemType, DEFAULT_TEST_ITEM_NAME, DEFAULT_CHANNEL_TEST_PRIORITY,
+                mockedMemoryAvailablePercentValue);
+    }
+
+    @Test
+    public void assertChannelSwapAvailableIsUpdated() {
+        String channnelID = SystemInfoBindingConstants.CHANNEL_SWAP_AVAILABLE;
+        String acceptedItemType = "Number:DataAmount";
+
+        QuantityType<DataAmount> mockedSwapAvailableValue = new QuantityType<>(482, Units.MEBIBYTE);
+        when(mockedSystemInfo.getSwapAvailable()).thenReturn(mockedSwapAvailableValue);
+
+        initializeThingWithChannel(channnelID, acceptedItemType);
+        assertItemState(acceptedItemType, DEFAULT_TEST_ITEM_NAME, DEFAULT_CHANNEL_TEST_PRIORITY,
+                mockedSwapAvailableValue);
+    }
+
+    @Test
+    public void assertChannelSwapUsedIsUpdated() {
+        String channnelID = SystemInfoBindingConstants.CHANNEL_SWAP_USED;
+        String acceptedItemType = "Number:DataAmount";
+
+        QuantityType<DataAmount> mockedSwapUsedValue = new QuantityType<>(30, Units.MEBIBYTE);
+        when(mockedSystemInfo.getSwapUsed()).thenReturn(mockedSwapUsedValue);
+
+        initializeThingWithChannel(channnelID, acceptedItemType);
+        assertItemState(acceptedItemType, DEFAULT_TEST_ITEM_NAME, DEFAULT_CHANNEL_TEST_PRIORITY, mockedSwapUsedValue);
+    }
+
+    @Test
+    public void assertChannelSwapTotalIsUpdated() {
+        String channnelID = SystemInfoBindingConstants.CHANNEL_SWAP_TOTAL;
+        String acceptedItemType = "Number:DataAmount";
+
+        QuantityType<DataAmount> mockedSwapTotalValue = new QuantityType<>(512, Units.MEBIBYTE);
+        when(mockedSystemInfo.getSwapTotal()).thenReturn(mockedSwapTotalValue);
+
+        initializeThingWithChannel(channnelID, acceptedItemType);
+        assertItemState(acceptedItemType, DEFAULT_TEST_ITEM_NAME, DEFAULT_CHANNEL_TEST_PRIORITY, mockedSwapTotalValue);
+    }
+
+    @Test
+    public void assertChannelSwapAvailablePercentIsUpdated() {
+        String channnelID = SystemInfoBindingConstants.CHANNEL_SWAP_AVAILABLE_PERCENT;
+        String acceptedItemType = "Number";
+
+        PercentType mockedSwapAvailablePercentValue = new PercentType(94);
+        when(mockedSystemInfo.getSwapAvailablePercent()).thenReturn(mockedSwapAvailablePercentValue);
+
+        initializeThingWithChannel(channnelID, acceptedItemType);
+        assertItemState(acceptedItemType, DEFAULT_TEST_ITEM_NAME, DEFAULT_CHANNEL_TEST_PRIORITY,
+                mockedSwapAvailablePercentValue);
+    }
+
+    @Test
+    public void assertChannelStorageNameIsUpdated() throws DeviceNotFoundException {
+        String channnelID = SystemInfoBindingConstants.CHANNEL_STORAGE_NAME;
+        String acceptedItemType = "String";
+
+        StringType mockedStorageName = new StringType("Mocked Storage Name");
+        when(mockedSystemInfo.getStorageName(DEFAULT_DEVICE_INDEX)).thenReturn(mockedStorageName);
+
+        initializeThingWithChannel(channnelID, acceptedItemType);
+        assertItemState(acceptedItemType, DEFAULT_TEST_ITEM_NAME, DEFAULT_CHANNEL_TEST_PRIORITY, mockedStorageName);
+    }
+
+    @Test
+    public void assertChannelStorageTypeIsUpdated() throws DeviceNotFoundException {
+        String channnelID = SystemInfoBindingConstants.CHANNEL_STORAGE_TYPE;
+        String acceptedItemType = "String";
+
+        StringType mockedStorageType = new StringType("Mocked Storage Type");
+        when(mockedSystemInfo.getStorageType(DEFAULT_DEVICE_INDEX)).thenReturn(mockedStorageType);
+
+        initializeThingWithChannel(channnelID, acceptedItemType);
+        assertItemState(acceptedItemType, DEFAULT_TEST_ITEM_NAME, DEFAULT_CHANNEL_TEST_PRIORITY, mockedStorageType);
+    }
+
+    @Test
+    public void assertChannelStorageDescriptionIsUpdated() throws DeviceNotFoundException {
+        String channnelID = SystemInfoBindingConstants.CHANNEL_STORAGE_DESCRIPTION;
+        String acceptedItemType = "String";
+
+        StringType mockedStorageDescription = new StringType("Mocked Storage Description");
+        when(mockedSystemInfo.getStorageDescription(DEFAULT_DEVICE_INDEX)).thenReturn(mockedStorageDescription);
+
+        initializeThingWithChannel(channnelID, acceptedItemType);
+        assertItemState(acceptedItemType, DEFAULT_TEST_ITEM_NAME, DEFAULT_CHANNEL_TEST_PRIORITY,
+                mockedStorageDescription);
+    }
+
+    @Test
+    public void assertChannelStorageAvailableIsUpdated() throws DeviceNotFoundException {
+        String channnelID = SystemInfoBindingConstants.CHANNEL_STORAGE_AVAILABLE;
+        String acceptedItemType = "Number:DataAmount";
+
+        QuantityType<DataAmount> mockedStorageAvailableValue = new QuantityType<>(2000, Units.MEBIBYTE);
+        when(mockedSystemInfo.getStorageAvailable(DEFAULT_DEVICE_INDEX)).thenReturn(mockedStorageAvailableValue);
+
+        initializeThingWithChannel(channnelID, acceptedItemType);
+        assertItemState(acceptedItemType, DEFAULT_TEST_ITEM_NAME, DEFAULT_CHANNEL_TEST_PRIORITY,
+                mockedStorageAvailableValue);
+    }
+
+    @Test
+    public void assertChannelStorageUsedIsUpdated() throws DeviceNotFoundException {
+        String channnelID = SystemInfoBindingConstants.CHANNEL_STORAGE_USED;
+        String acceptedItemType = "Number:DataAmount";
+
+        QuantityType<DataAmount> mockedStorageUsedValue = new QuantityType<>(500, Units.MEBIBYTE);
+        when(mockedSystemInfo.getStorageUsed(DEFAULT_DEVICE_INDEX)).thenReturn(mockedStorageUsedValue);
+
+        initializeThingWithChannel(channnelID, acceptedItemType);
+        assertItemState(acceptedItemType, DEFAULT_TEST_ITEM_NAME, DEFAULT_CHANNEL_TEST_PRIORITY,
+                mockedStorageUsedValue);
+    }
+
+    @Test
+    public void assertChannelStorageTotalIsUpdated() throws DeviceNotFoundException {
+        String channnelID = SystemInfoBindingConstants.CHANNEL_STORAGE_TOTAL;
+        String acceptedItemType = "Number:DataAmount";
+
+        QuantityType<DataAmount> mockedStorageTotalValue = new QuantityType<>(2500, Units.MEBIBYTE);
+        when(mockedSystemInfo.getStorageTotal(DEFAULT_DEVICE_INDEX)).thenReturn(mockedStorageTotalValue);
+
+        initializeThingWithChannel(channnelID, acceptedItemType);
+        assertItemState(acceptedItemType, DEFAULT_TEST_ITEM_NAME, DEFAULT_CHANNEL_TEST_PRIORITY,
+                mockedStorageTotalValue);
+    }
+
+    @Test
+    public void assertChannelStorageAvailablePercentIsUpdated() throws DeviceNotFoundException {
+        String channnelID = SystemInfoBindingConstants.CHANNEL_STORAGE_AVAILABLE_PERCENT;
+        String acceptedItemType = "Number";
+
+        PercentType mockedStorageAvailablePercent = new PercentType(20);
+        when(mockedSystemInfo.getStorageAvailablePercent(DEFAULT_DEVICE_INDEX))
+                .thenReturn(mockedStorageAvailablePercent);
+
+        initializeThingWithChannel(channnelID, acceptedItemType);
+        assertItemState(acceptedItemType, DEFAULT_TEST_ITEM_NAME, DEFAULT_CHANNEL_TEST_PRIORITY,
+                mockedStorageAvailablePercent);
+    }
+
+    @Test
+    public void assertChannelDriveNameIsUpdated() throws DeviceNotFoundException {
+        String channelID = SystemInfoBindingConstants.CHANNEL_DRIVE_NAME;
+        String acceptedItemType = "String";
+
+        StringType mockedDriveNameValue = new StringType("Mocked Drive Name");
+        when(mockedSystemInfo.getDriveName(DEFAULT_DEVICE_INDEX)).thenReturn(mockedDriveNameValue);
+
+        initializeThingWithChannel(channelID, acceptedItemType);
+        assertItemState(acceptedItemType, DEFAULT_TEST_ITEM_NAME, DEFAULT_CHANNEL_TEST_PRIORITY, mockedDriveNameValue);
+    }
+
+    @Test
+    public void assertChannelDriveModelIsUpdated() throws DeviceNotFoundException {
+        String channelID = SystemInfoBindingConstants.CHANNEL_DRIVE_MODEL;
+        String acceptedItemType = "String";
+
+        StringType mockedDriveModelValue = new StringType("Mocked Drive Model");
+        when(mockedSystemInfo.getDriveModel(DEFAULT_DEVICE_INDEX)).thenReturn(mockedDriveModelValue);
+
+        initializeThingWithChannel(channelID, acceptedItemType);
+        assertItemState(acceptedItemType, DEFAULT_TEST_ITEM_NAME, DEFAULT_CHANNEL_TEST_PRIORITY, mockedDriveModelValue);
+    }
+
+    @Test
+    public void assertChannelDriveSerialIsUpdated() throws DeviceNotFoundException {
+        String channelID = SystemInfoBindingConstants.CHANNEL_DRIVE_SERIAL;
+        String acceptedItemType = "String";
+
+        StringType mockedDriveSerialNumber = new StringType("Mocked Drive Serial Number");
+        when(mockedSystemInfo.getDriveSerialNumber(DEFAULT_DEVICE_INDEX)).thenReturn(mockedDriveSerialNumber);
+
+        initializeThingWithChannel(channelID, acceptedItemType);
+        assertItemState(acceptedItemType, DEFAULT_TEST_ITEM_NAME, DEFAULT_CHANNEL_TEST_PRIORITY,
+                mockedDriveSerialNumber);
+    }
+
+    // Re-enable this previously disabled test, as it is not relying on hardware anymore, but a mocked object
+    // There is a bug opened for this issue - https://github.com/dblock/oshi/issues/185
+    @Test
+    public void assertChannelSensorsCpuTempIsUpdated() {
+        String channnelID = SystemInfoBindingConstants.CHANNEL_SENSORS_CPU_TEMPERATURE;
+        String acceptedItemType = "Number:Temperature";
+
+        QuantityType<Temperature> mockedSensorsCpuTemperatureValue = new QuantityType<>(60, SIUnits.CELSIUS);
+        when(mockedSystemInfo.getSensorsCpuTemperature()).thenReturn(mockedSensorsCpuTemperatureValue);
+
+        initializeThingWithChannel(channnelID, acceptedItemType);
+        assertItemState(acceptedItemType, DEFAULT_TEST_ITEM_NAME, DEFAULT_CHANNEL_TEST_PRIORITY,
+                mockedSensorsCpuTemperatureValue);
+    }
+
+    @Test
+    public void assertChannelSensorsCpuVoltageIsUpdated() {
+        String channnelID = SystemInfoBindingConstants.CHANNEL_SENOSRS_CPU_VOLTAGE;
+        String acceptedItemType = "Number:ElectricPotential";
+
+        QuantityType<ElectricPotential> mockedSensorsCpuVoltageValue = new QuantityType<>(1000, Units.VOLT);
+        when(mockedSystemInfo.getSensorsCpuVoltage()).thenReturn(mockedSensorsCpuVoltageValue);
+
+        initializeThingWithChannel(channnelID, acceptedItemType);
+        assertItemState(acceptedItemType, DEFAULT_TEST_ITEM_NAME, DEFAULT_CHANNEL_TEST_PRIORITY,
+                mockedSensorsCpuVoltageValue);
+    }
+
+    @Test
+    public void assertChannelSensorsFanSpeedIsUpdated() throws DeviceNotFoundException {
+        String channnelID = SystemInfoBindingConstants.CHANNEL_SENSORS_FAN_SPEED;
+        String acceptedItemType = "Number";
+
+        DecimalType mockedSensorsCpuFanSpeedValue = new DecimalType(180);
+        when(mockedSystemInfo.getSensorsFanSpeed(DEFAULT_DEVICE_INDEX)).thenReturn(mockedSensorsCpuFanSpeedValue);
+
+        initializeThingWithChannel(channnelID, acceptedItemType);
+        assertItemState(acceptedItemType, DEFAULT_TEST_ITEM_NAME, DEFAULT_CHANNEL_TEST_PRIORITY,
+                mockedSensorsCpuFanSpeedValue);
+    }
+
+    @Test
+    public void assertChannelBatteryNameIsUpdated() throws DeviceNotFoundException {
+        String channnelID = SystemInfoBindingConstants.CHANNEL_BATTERY_NAME;
+        String acceptedItemType = "String";
+
+        StringType mockedBatteryName = new StringType("Mocked Battery Name");
+        when(mockedSystemInfo.getBatteryName(DEFAULT_DEVICE_INDEX)).thenReturn(mockedBatteryName);
+
+        initializeThingWithChannel(channnelID, acceptedItemType);
+        assertItemState(acceptedItemType, DEFAULT_TEST_ITEM_NAME, DEFAULT_CHANNEL_TEST_PRIORITY, mockedBatteryName);
+    }
+
+    @Test
+    public void assertChannelBatteryRemainingCapacityIsUpdated() throws DeviceNotFoundException {
+        String channnelID = SystemInfoBindingConstants.CHANNEL_BATTERY_REMAINING_CAPACITY;
+        String acceptedItemType = "Number";
+
+        PercentType mockedBatteryRemainingCapacity = new PercentType(20);
+        when(mockedSystemInfo.getBatteryRemainingCapacity(DEFAULT_DEVICE_INDEX))
+                .thenReturn(mockedBatteryRemainingCapacity);
+
+        initializeThingWithChannel(channnelID, acceptedItemType);
+        assertItemState(acceptedItemType, DEFAULT_TEST_ITEM_NAME, DEFAULT_CHANNEL_TEST_PRIORITY,
+                mockedBatteryRemainingCapacity);
+    }
+
+    @Test
+    public void assertChannelBatteryRemainingTimeIsUpdated() throws DeviceNotFoundException {
+        String channnelID = SystemInfoBindingConstants.CHANNEL_BATTERY_REMAINING_TIME;
+        String acceptedItemType = "Number:Time";
+
+        QuantityType<Time> mockedBatteryRemainingTime = new QuantityType<>(3600, Units.MINUTE);
+        when(mockedSystemInfo.getBatteryRemainingTime(DEFAULT_DEVICE_INDEX)).thenReturn(mockedBatteryRemainingTime);
+
+        initializeThingWithChannel(channnelID, acceptedItemType);
+        assertItemState(acceptedItemType, DEFAULT_TEST_ITEM_NAME, DEFAULT_CHANNEL_TEST_PRIORITY,
+                mockedBatteryRemainingTime);
+    }
+
+    @Test
+    public void assertChannelDisplayInformationIsUpdated() throws DeviceNotFoundException {
+        String channnelID = SystemInfoBindingConstants.CHANNEL_DISPLAY_INFORMATION;
+        String acceptedItemType = "String";
+
+        StringType mockedDisplayInfo = new StringType("Mocked Display Information");
+        when(mockedSystemInfo.getDisplayInformation(DEFAULT_DEVICE_INDEX)).thenReturn(mockedDisplayInfo);
+
+        initializeThingWithChannel(channnelID, acceptedItemType);
+        assertItemState(acceptedItemType, DEFAULT_TEST_ITEM_NAME, DEFAULT_CHANNEL_TEST_PRIORITY, mockedDisplayInfo);
+    }
+
+    @Test
+    public void assertChannelNetworkIpIsUpdated() throws DeviceNotFoundException {
+        String channnelID = SystemInfoBindingConstants.CHANNEL_NETWORK_IP;
+        String acceptedItemType = "String";
+
+        StringType mockedNetworkIp = new StringType("192.168.1.0");
+        when(mockedSystemInfo.getNetworkIp(DEFAULT_DEVICE_INDEX)).thenReturn(mockedNetworkIp);
+
+        initializeThingWithChannel(channnelID, acceptedItemType);
+        assertItemState(acceptedItemType, DEFAULT_TEST_ITEM_NAME, DEFAULT_CHANNEL_TEST_PRIORITY, mockedNetworkIp);
+    }
+
+    @Test
+    public void assertChannelNetworkMacIsUpdated() throws DeviceNotFoundException {
+        String channnelID = SystemInfoBindingConstants.CHANNEL_NETWORK_MAC;
+        String acceptedItemType = "String";
+
+        StringType mockedNetworkMacValue = new StringType("AB-10-11-12-13-14");
+        when(mockedSystemInfo.getNetworkMac(DEFAULT_DEVICE_INDEX)).thenReturn(mockedNetworkMacValue);
+
+        initializeThingWithChannel(channnelID, acceptedItemType);
+        assertItemState(acceptedItemType, DEFAULT_TEST_ITEM_NAME, DEFAULT_CHANNEL_TEST_PRIORITY, mockedNetworkMacValue);
+    }
+
+    @Test
+    public void assertChannelNetworkDataSentIsUpdated() throws DeviceNotFoundException {
+        String channnelID = SystemInfoBindingConstants.CHANNEL_NETWORK_DATA_SENT;
+        String acceptedItemType = "Number:DataAmount";
+
+        QuantityType<DataAmount> mockedNetworkDataSent = new QuantityType<>(1000, Units.MEBIBYTE);
+        when(mockedSystemInfo.getNetworkDataSent(DEFAULT_DEVICE_INDEX)).thenReturn(mockedNetworkDataSent);
+
+        initializeThingWithChannel(channnelID, acceptedItemType);
+        assertItemState(acceptedItemType, DEFAULT_TEST_ITEM_NAME, DEFAULT_CHANNEL_TEST_PRIORITY, mockedNetworkDataSent);
+    }
+
+    @Test
+    public void assertChannelNetworkDataReceivedIsUpdated() throws DeviceNotFoundException {
+        String channnelID = SystemInfoBindingConstants.CHANNEL_NETWORK_DATA_RECEIVED;
+        String acceptedItemType = "Number:DataAmount";
+
+        QuantityType<DataAmount> mockedNetworkDataReceiveed = new QuantityType<>(800, Units.MEBIBYTE);
+        when(mockedSystemInfo.getNetworkDataReceived(DEFAULT_DEVICE_INDEX)).thenReturn(mockedNetworkDataReceiveed);
+
+        initializeThingWithChannel(channnelID, acceptedItemType);
+        assertItemState(acceptedItemType, DEFAULT_TEST_ITEM_NAME, DEFAULT_CHANNEL_TEST_PRIORITY,
+                mockedNetworkDataReceiveed);
+    }
+
+    @Test
+    public void assertChannelNetworkPacketsSentIsUpdated() throws DeviceNotFoundException {
+        String channnelID = SystemInfoBindingConstants.CHANNEL_NETWORK_PACKETS_SENT;
+        String acceptedItemType = "Number";
+
+        DecimalType mockedNetworkPacketsSent = new DecimalType(50);
+        when(mockedSystemInfo.getNetworkPacketsSent(DEFAULT_DEVICE_INDEX)).thenReturn(mockedNetworkPacketsSent);
+
+        initializeThingWithChannel(channnelID, acceptedItemType);
+        assertItemState(acceptedItemType, DEFAULT_TEST_ITEM_NAME, DEFAULT_CHANNEL_TEST_PRIORITY,
+                mockedNetworkPacketsSent);
+    }
+
+    @Test
+    public void assertChannelNetworkPacketsReceivedIsUpdated() throws DeviceNotFoundException {
+        String channnelID = SystemInfoBindingConstants.CHANNEL_NETWORK_PACKETS_RECEIVED;
+        String acceptedItemType = "Number";
+
+        DecimalType mockedNetworkPacketsReceived = new DecimalType(48);
+        when(mockedSystemInfo.getNetworkPacketsReceived(DEFAULT_DEVICE_INDEX)).thenReturn(mockedNetworkPacketsReceived);
+
+        initializeThingWithChannel(channnelID, acceptedItemType);
+        assertItemState(acceptedItemType, DEFAULT_TEST_ITEM_NAME, DEFAULT_CHANNEL_TEST_PRIORITY,
+                mockedNetworkPacketsReceived);
+    }
+
+    @Test
+    public void assertChannelNetworkNetworkNameIsUpdated() throws DeviceNotFoundException {
+        String channnelID = SystemInfoBindingConstants.CHANNEL_NETWORK_NAME;
+        String acceptedItemType = "String";
+
+        StringType mockedNetworkName = new StringType("MockN-AQ34");
+        when(mockedSystemInfo.getNetworkName(DEFAULT_DEVICE_INDEX)).thenReturn(mockedNetworkName);
+
+        initializeThingWithChannel(channnelID, acceptedItemType);
+        assertItemState(acceptedItemType, DEFAULT_TEST_ITEM_NAME, DEFAULT_CHANNEL_TEST_PRIORITY, mockedNetworkName);
+    }
+
+    @Test
+    public void assertChannelNetworkNetworkDisplayNameIsUpdated() throws DeviceNotFoundException {
+        String channnelID = SystemInfoBindingConstants.CHANNEL_NETWORK_ADAPTER_NAME;
+        String acceptedItemType = "String";
+
+        StringType mockedNetworkAdapterName = new StringType("Mocked Network Adapter Name");
+        when(mockedSystemInfo.getNetworkDisplayName(DEFAULT_DEVICE_INDEX)).thenReturn(mockedNetworkAdapterName);
+
+        initializeThingWithChannel(channnelID, acceptedItemType);
+        assertItemState(acceptedItemType, DEFAULT_TEST_ITEM_NAME, DEFAULT_CHANNEL_TEST_PRIORITY,
+                mockedNetworkAdapterName);
+    }
+
+    class SystemInfoDiscoveryServiceMock extends SystemInfoDiscoveryService {
+        String hostname;
+
+        SystemInfoDiscoveryServiceMock(String hostname) {
+            super();
+            this.hostname = hostname;
+        }
+
+        @Override
+        protected String getHostName() throws UnknownHostException {
+            if ("unresolved".equals(hostname)) {
+                throw new UnknownHostException();
+            }
+            return hostname;
+        }
+
+        @Override
+        public void startScan() {
+            super.startScan();
+        }
+    }
+
+    @Test
+    public void testDiscoveryWithInvalidHostname() {
+        String hostname = "Hilo.fritz.box";
+        String expectedHostname = "Hilo_fritz_box";
+
+        testDiscoveryService(expectedHostname, hostname);
+    }
+
+    @Test
+    public void testDiscoveryWithValidHostname() {
+        String hostname = "MyComputer";
+        String expectedHostname = "MyComputer";
+
+        testDiscoveryService(expectedHostname, hostname);
+    }
+
+    @Test
+    public void testDiscoveryWithUnresolvedHostname() {
+        String hostname = "unresolved";
+        String expectedHostname = SystemInfoDiscoveryService.DEFAULT_THING_ID;
+
+        testDiscoveryService(expectedHostname, hostname);
+    }
+
+    @Test
+    public void testDiscoveryWithEmptyHostnameString() {
+        String hostname = "";
+        String expectedHostname = SystemInfoDiscoveryService.DEFAULT_THING_ID;
+
+        testDiscoveryService(expectedHostname, hostname);
+    }
+
+    private void testDiscoveryService(String expectedHostname, String hostname) {
+        SystemInfoDiscoveryService discoveryService = getService(DiscoveryService.class,
+                SystemInfoDiscoveryService.class);
+        waitForAssert(() -> {
+            assertThat(discoveryService, is(notNullValue()));
+        });
+        SystemInfoDiscoveryServiceMock discoveryServiceMock = new SystemInfoDiscoveryServiceMock(hostname);
+        if (discoveryService != null) {
+            unregisterService(DiscoveryService.class);
+        }
+        registerService(discoveryServiceMock, DiscoveryService.class.getName(), new Hashtable<>());
+
+        ThingTypeUID computerType = SystemInfoBindingConstants.THING_TYPE_COMPUTER;
+        ThingUID computerUID = new ThingUID(computerType, expectedHostname);
+
+        discoveryServiceMock.startScan();
+
+        Inbox inbox = getService(Inbox.class);
+        waitForAssert(() -> {
+            assertThat(inbox, is(notNullValue()));
+        });
+
+        if (inbox == null) {
+            return;
+        }
+
+        waitForAssert(() -> {
+            List<DiscoveryResult> results = inbox.stream().filter(InboxPredicates.forThingUID(computerUID)).toList();
+            assertFalse(results.isEmpty(), "No Thing with UID " + computerUID.getAsString() + " in inbox");
+        });
+
+        inbox.approve(computerUID, SystemInfoDiscoveryService.DEFAULT_THING_LABEL, null);
+
+        waitForAssert(() -> {
+            systeminfoThing = thingRegistry.get(computerUID);
+            assertThat(systeminfoThing, is(notNullValue()));
+        });
+
+        Thing thing = systeminfoThing;
+        if (thing == null) {
+            return;
+        }
+
+        waitForAssert(() -> {
+            assertThat("Thing is not initialized.", thing.getStatus(), is(equalTo(ThingStatus.ONLINE)));
+        });
+    }
+
+    @Test
+    public void assertChannelProcessThreadsIsUpdatedWithPIDse() throws DeviceNotFoundException {
+        String channnelID = SystemInfoBindingConstants.CHANNEL_PROCESS_THREADS;
+        String acceptedItemType = "Number";
+        // The pid of the System idle process in Windows
+        int pid = 0;
+
+        DecimalType mockedProcessThreadsCount = new DecimalType(4);
+        when(mockedSystemInfo.getProcessThreads(pid)).thenReturn(mockedProcessThreadsCount);
+
+        initializeThingWithChannelAndPID(channnelID, acceptedItemType, pid);
+        assertItemState(acceptedItemType, DEFAULT_TEST_ITEM_NAME, DEFAULT_CHANNEL_TEST_PRIORITY,
+                mockedProcessThreadsCount);
+    }
+
+    @Test
+    public void assertChannelProcessPathIsUpdatedWithPIDset() throws DeviceNotFoundException {
+        String channnelID = SystemInfoBindingConstants.CHANNEL_PROCESS_PATH;
+        String acceptedItemType = "String";
+        // The pid of the System idle process in Windows
+        int pid = 0;
+
+        StringType mockedProcessPath = new StringType("C:\\Users\\MockedUser\\Process");
+        when(mockedSystemInfo.getProcessPath(pid)).thenReturn(mockedProcessPath);
+
+        initializeThingWithChannelAndPID(channnelID, acceptedItemType, pid);
+        assertItemState(acceptedItemType, DEFAULT_TEST_ITEM_NAME, DEFAULT_CHANNEL_TEST_PRIORITY, mockedProcessPath);
+    }
+
+    @Test
+    public void assertChannelProcessNameIsUpdatedWithPIDset() throws DeviceNotFoundException {
+        String channnelID = SystemInfoBindingConstants.CHANNEL_PROCESS_NAME;
+        String acceptedItemType = "String";
+        // The pid of the System idle process in Windows
+        int pid = 0;
+
+        StringType mockedProcessName = new StringType("MockedProcess.exe");
+        when(mockedSystemInfo.getProcessName(pid)).thenReturn(mockedProcessName);
+
+        initializeThingWithChannelAndPID(channnelID, acceptedItemType, pid);
+        assertItemState(acceptedItemType, DEFAULT_TEST_ITEM_NAME, DEFAULT_CHANNEL_TEST_PRIORITY, mockedProcessName);
+    }
+
+    @Test
+    public void assertChannelProcessMemoryIsUpdatedWithPIDset() throws DeviceNotFoundException {
+        String channnelID = SystemInfoBindingConstants.CHANNEL_PROCESS_MEMORY;
+        String acceptedItemType = "Number:DataAmount";
+        // The pid of the System idle process in Windows
+        int pid = 0;
+
+        QuantityType<DataAmount> mockedProcessMemory = new QuantityType<>(450, Units.MEBIBYTE);
+        when(mockedSystemInfo.getProcessMemoryUsage(pid)).thenReturn(mockedProcessMemory);
+
+        initializeThingWithChannelAndPID(channnelID, acceptedItemType, pid);
+        assertItemState(acceptedItemType, DEFAULT_TEST_ITEM_NAME, DEFAULT_CHANNEL_TEST_PRIORITY, mockedProcessMemory);
+    }
+
+    @Test
+    public void assertChannelProcessLoadIsUpdatedWithPIDset() throws DeviceNotFoundException {
+        String channnelID = SystemInfoBindingConstants.CHANNEL_PROCESS_LOAD;
+        String acceptedItemType = "Number";
+        // The pid of the System idle process in Windows
+        int pid = 0;
+
+        DecimalType mockedProcessLoad = new DecimalType(3);
+        when(mockedSystemInfo.getProcessCpuUsage(pid)).thenReturn(mockedProcessLoad);
+
+        initializeThingWithChannelAndPID(channnelID, acceptedItemType, pid);
+        assertItemState(acceptedItemType, DEFAULT_TEST_ITEM_NAME, DEFAULT_CHANNEL_TEST_PRIORITY, mockedProcessLoad);
+    }
+
+    @Test
+    public void testThingHandlesChannelPriorityChange() {
+        String priorityKey = "priority";
+        String pidKey = "pid";
+        String initialPriority = DEFAULT_CHANNEL_TEST_PRIORITY; // Evaluates to High
+        String newPriority = "Low";
+
+        String acceptedItemType = "Number";
+        initializeThingWithChannel(DEFAULT_TEST_CHANNEL_ID, acceptedItemType);
+
+        Thing thing = systeminfoThing;
+        if (thing == null) {
+            throw new AssertionError("Thing is null");
+        }
+        Channel channel = thing.getChannel(DEFAULT_TEST_CHANNEL_ID);
+        if (channel == null) {
+            throw new AssertionError("Channel '" + DEFAULT_TEST_CHANNEL_ID + "' is null");
+        }
+
+        ThingHandler thingHandler = thing.getHandler();
+        if (thingHandler == null) {
+            throw new AssertionError("Thing handler is null");
+        }
+        if (!(thingHandler.getClass().equals(SystemInfoHandler.class))) {
+            throw new AssertionError("Thing handler not of class SystemInfoHandler");
+        }
+        SystemInfoHandler handler = (SystemInfoHandler) thingHandler;
+        waitForAssert(() -> {
+            assertThat("The initial priority of channel " + channel.getUID() + " is not as expected.",
+                    channel.getConfiguration().get(priorityKey), is(equalTo(initialPriority)));
+            assertThat(handler.getHighPriorityChannels().contains(channel.getUID()), is(true));
+        });
+
+        // Change the priority of a channel, keep the pid
+        Configuration updatedConfig = new Configuration();
+        updatedConfig.put(priorityKey, newPriority);
+        updatedConfig.put(pidKey, channel.getConfiguration().get(pidKey));
+        Channel updatedChannel = ChannelBuilder.create(channel.getUID(), channel.getAcceptedItemType())
+                .withType(channel.getChannelTypeUID()).withKind(channel.getKind()).withConfiguration(updatedConfig)
+                .build();
+
+        Thing updatedThing = ThingBuilder.create(thing.getThingTypeUID(), thing.getUID())
+                .withConfiguration(thing.getConfiguration()).withChannel(updatedChannel).build();
+
+        handler.thingUpdated(updatedThing);
+
+        waitForAssert(() -> {
+            assertThat("The prority of the channel was not updated: ", channel.getConfiguration().get(priorityKey),
+                    is(equalTo(newPriority)));
+            assertThat(handler.getLowPriorityChannels().contains(channel.getUID()), is(true));
+        });
+    }
+}
diff --git a/itests/org.openhab.binding.systeminfo.tests/src/main/java/org/openhab/binding/systeminfo/test/SysteminfoOSGiTest.java b/itests/org.openhab.binding.systeminfo.tests/src/main/java/org/openhab/binding/systeminfo/test/SysteminfoOSGiTest.java
deleted file mode 100644 (file)
index 0c83947..0000000
+++ /dev/null
@@ -1,1173 +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.test;
-
-import static java.lang.Thread.sleep;
-import static org.hamcrest.CoreMatchers.*;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.mockito.Mockito.*;
-
-import java.math.BigDecimal;
-import java.net.UnknownHostException;
-import java.util.Hashtable;
-import java.util.List;
-import java.util.Map;
-
-import javax.measure.quantity.ElectricPotential;
-import javax.measure.quantity.Frequency;
-import javax.measure.quantity.Temperature;
-import javax.measure.quantity.Time;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.eclipse.jdt.annotation.Nullable;
-import org.junit.jupiter.api.AfterEach;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.extension.ExtendWith;
-import org.mockito.Mock;
-import org.mockito.junit.jupiter.MockitoExtension;
-import org.mockito.junit.jupiter.MockitoSettings;
-import org.mockito.quality.Strictness;
-import org.openhab.binding.systeminfo.internal.SysteminfoBindingConstants;
-import org.openhab.binding.systeminfo.internal.SysteminfoHandlerFactory;
-import org.openhab.binding.systeminfo.internal.discovery.SysteminfoDiscoveryService;
-import org.openhab.binding.systeminfo.internal.handler.SysteminfoHandler;
-import org.openhab.binding.systeminfo.internal.model.DeviceNotFoundException;
-import org.openhab.binding.systeminfo.internal.model.OSHISysteminfo;
-import org.openhab.binding.systeminfo.internal.model.SysteminfoInterface;
-import org.openhab.core.config.core.Configuration;
-import org.openhab.core.config.discovery.DiscoveryResult;
-import org.openhab.core.config.discovery.DiscoveryService;
-import org.openhab.core.config.discovery.inbox.Inbox;
-import org.openhab.core.config.discovery.inbox.InboxPredicates;
-import org.openhab.core.i18n.UnitProvider;
-import org.openhab.core.items.GenericItem;
-import org.openhab.core.items.ItemNotFoundException;
-import org.openhab.core.items.ItemRegistry;
-import org.openhab.core.library.dimension.DataAmount;
-import org.openhab.core.library.items.NumberItem;
-import org.openhab.core.library.items.StringItem;
-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.openhab.core.test.java.JavaOSGiTest;
-import org.openhab.core.test.storage.VolatileStorageService;
-import org.openhab.core.thing.Channel;
-import org.openhab.core.thing.ChannelUID;
-import org.openhab.core.thing.ManagedThingProvider;
-import org.openhab.core.thing.Thing;
-import org.openhab.core.thing.ThingProvider;
-import org.openhab.core.thing.ThingRegistry;
-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.ThingHandler;
-import org.openhab.core.thing.binding.ThingHandlerFactory;
-import org.openhab.core.thing.binding.builder.ChannelBuilder;
-import org.openhab.core.thing.binding.builder.ThingBuilder;
-import org.openhab.core.thing.link.ItemChannelLink;
-import org.openhab.core.thing.link.ManagedItemChannelLinkProvider;
-import org.openhab.core.thing.type.ChannelKind;
-import org.openhab.core.thing.type.ChannelTypeUID;
-import org.openhab.core.types.State;
-import org.openhab.core.types.UnDefType;
-
-/**
- * OSGi tests for the {@link SysteminfoHandler}
- *
- * @author Svilen Valkanov - Initial contribution
- * @author Lyubomir Papazov - Created a mock systeminfo object. This way, access to the user's OS will not be required,
- *         but mock data will be used instead, avoiding potential errors from the OS queries.
- * @author Wouter Born - Migrate Groovy to Java tests
- * @author Mark Herwege - Processor frequency channels
- */
-@NonNullByDefault
-@ExtendWith(MockitoExtension.class)
-@MockitoSettings(strictness = Strictness.LENIENT)
-public class SysteminfoOSGiTest extends JavaOSGiTest {
-
-    private static final String DEFAULT_TEST_THING_NAME = "work";
-    private static final String DEFAULT_TEST_ITEM_NAME = "test";
-    private static final String DEFAULT_CHANNEL_TEST_PRIORITY = "High";
-    private static final int DEFAULT_CHANNEL_PID = -1;
-    private static final String DEFAULT_TEST_CHANNEL_ID = SysteminfoBindingConstants.CHANNEL_CPU_LOAD;
-    private static final int DEFAULT_DEVICE_INDEX = 0;
-
-    /**
-     * Refresh time in seconds for tasks with priority High.
-     * Default value for the parameter interval_high in the thing configuration
-     */
-    private static final int DEFAULT_TEST_INTERVAL_HIGH = 1;
-
-    /**
-     * Refresh time in seconds for tasks with priority Medium.
-     */
-    private static final int DEFAULT_TEST_INTERVAL_MEDIUM = 3;
-
-    private @Nullable Thing systeminfoThing;
-    private @Nullable GenericItem testItem;
-
-    private @Mock @NonNullByDefault({}) OSHISysteminfo mockedSystemInfo;
-    private @NonNullByDefault({}) SysteminfoHandlerFactory systeminfoHandlerFactory;
-    private @NonNullByDefault({}) ThingRegistry thingRegistry;
-    private @NonNullByDefault({}) ItemRegistry itemRegistry;
-    private @NonNullByDefault({}) ManagedThingProvider managedThingProvider;
-    private @NonNullByDefault({}) ManagedItemChannelLinkProvider itemChannelLinkProvider;
-    private @NonNullByDefault({}) UnitProvider unitProvider;
-    private @NonNullByDefault({}) VolatileStorageService volatileStorageService;
-
-    @BeforeEach
-    public void setUp() {
-        volatileStorageService = new VolatileStorageService();
-        registerService(volatileStorageService);
-
-        // Preparing the mock with OS properties, that are used in the initialize method of SysteminfoHandler
-        // Make this lenient because the assertInvalidThingConfigurationValuesAreHandled test does not require them
-        lenient().when(mockedSystemInfo.getCpuLogicalCores()).thenReturn(new DecimalType(1));
-        lenient().when(mockedSystemInfo.getCpuPhysicalCores()).thenReturn(new DecimalType(1));
-        lenient().when(mockedSystemInfo.getOsFamily()).thenReturn(new StringType("Mock OS"));
-        lenient().when(mockedSystemInfo.getOsManufacturer()).thenReturn(new StringType("Mock OS Manufacturer"));
-        lenient().when(mockedSystemInfo.getOsVersion()).thenReturn(new StringType("Mock Os Version"));
-        // Following mock method returns will make sure the thing does not get recreated with extra channels
-        lenient().when(mockedSystemInfo.getNetworkIFCount()).thenReturn(1);
-        lenient().when(mockedSystemInfo.getDisplayCount()).thenReturn(1);
-        lenient().when(mockedSystemInfo.getFileOSStoreCount()).thenReturn(1);
-        lenient().when(mockedSystemInfo.getPowerSourceCount()).thenReturn(1);
-        lenient().when(mockedSystemInfo.getDriveCount()).thenReturn(1);
-        lenient().when(mockedSystemInfo.getFanCount()).thenReturn(1);
-
-        registerService(mockedSystemInfo);
-
-        waitForAssert(() -> {
-            systeminfoHandlerFactory = getService(ThingHandlerFactory.class, SysteminfoHandlerFactory.class);
-            assertThat(systeminfoHandlerFactory, is(notNullValue()));
-        });
-
-        if (systeminfoHandlerFactory != null) {
-            // Unbind oshiSystemInfo service and bind the mock service to make the systeminfo binding tests independent
-            // of the external OSHI library
-            SysteminfoInterface oshiSystemInfo = getService(SysteminfoInterface.class);
-            if (oshiSystemInfo != null) {
-                systeminfoHandlerFactory.unbindSystemInfo(oshiSystemInfo);
-            }
-            systeminfoHandlerFactory.bindSystemInfo(mockedSystemInfo);
-        }
-
-        waitForAssert(() -> {
-            thingRegistry = getService(ThingRegistry.class);
-            assertThat(thingRegistry, is(notNullValue()));
-        });
-
-        waitForAssert(() -> {
-            itemRegistry = getService(ItemRegistry.class);
-            assertThat(itemRegistry, is(notNullValue()));
-        });
-
-        waitForAssert(() -> {
-            managedThingProvider = getService(ThingProvider.class, ManagedThingProvider.class);
-            assertThat(managedThingProvider, is(notNullValue()));
-        });
-
-        waitForAssert(() -> {
-            itemChannelLinkProvider = getService(ManagedItemChannelLinkProvider.class);
-            assertThat(itemChannelLinkProvider, is(notNullValue()));
-        });
-
-        waitForAssert(() -> {
-            unitProvider = getService(UnitProvider.class);
-            assertThat(unitProvider, is(notNullValue()));
-        });
-    }
-
-    @AfterEach
-    public void tearDown() {
-        Thing thing = systeminfoThing;
-        if (thing != null) {
-            // Remove the systeminfo thing. The handler will also be disposed automatically
-            Thing removedThing = thingRegistry.forceRemove(thing.getUID());
-            assertThat("The systeminfo thing cannot be deleted", removedThing, is(notNullValue()));
-            waitForAssert(() -> {
-                ThingHandler systemInfoHandler = thing.getHandler();
-                assertThat(systemInfoHandler, is(nullValue()));
-            });
-            managedThingProvider.remove(thing.getUID());
-        }
-
-        if (testItem != null) {
-            itemRegistry.remove(DEFAULT_TEST_ITEM_NAME);
-        }
-
-        unregisterService(mockedSystemInfo);
-        unregisterService(volatileStorageService);
-    }
-
-    private void initializeThingWithChannelAndPID(String channelID, String acceptedItemType, int pid) {
-        Configuration thingConfig = new Configuration();
-        thingConfig.put(SysteminfoBindingConstants.HIGH_PRIORITY_REFRESH_TIME,
-                new BigDecimal(DEFAULT_TEST_INTERVAL_HIGH));
-        thingConfig.put(SysteminfoBindingConstants.MEDIUM_PRIORITY_REFRESH_TIME,
-                new BigDecimal(DEFAULT_TEST_INTERVAL_MEDIUM));
-        String priority = DEFAULT_CHANNEL_TEST_PRIORITY;
-
-        initializeThing(thingConfig, channelID, acceptedItemType, priority, pid);
-    }
-
-    private void initializeThingWithChannelAndPriority(String channelID, String acceptedItemType, String priority) {
-        Configuration thingConfig = new Configuration();
-        thingConfig.put(SysteminfoBindingConstants.HIGH_PRIORITY_REFRESH_TIME,
-                new BigDecimal(DEFAULT_TEST_INTERVAL_HIGH));
-        thingConfig.put(SysteminfoBindingConstants.MEDIUM_PRIORITY_REFRESH_TIME,
-                new BigDecimal(DEFAULT_TEST_INTERVAL_MEDIUM));
-        int pid = DEFAULT_CHANNEL_PID;
-
-        initializeThing(thingConfig, channelID, acceptedItemType, priority, pid);
-    }
-
-    private void initializeThingWithConfiguration(Configuration config) {
-        String priority = DEFAULT_CHANNEL_TEST_PRIORITY;
-        String channelID = DEFAULT_TEST_CHANNEL_ID;
-        String acceptedItemType = "String";
-        int pid = DEFAULT_CHANNEL_PID;
-
-        initializeThing(config, channelID, acceptedItemType, priority, pid);
-    }
-
-    private void initializeThingWithChannel(String channelID, String acceptedItemType) {
-        Configuration thingConfig = new Configuration();
-        thingConfig.put(SysteminfoBindingConstants.HIGH_PRIORITY_REFRESH_TIME,
-                new BigDecimal(DEFAULT_TEST_INTERVAL_HIGH));
-        thingConfig.put(SysteminfoBindingConstants.MEDIUM_PRIORITY_REFRESH_TIME,
-                new BigDecimal(DEFAULT_TEST_INTERVAL_MEDIUM));
-
-        String priority = DEFAULT_CHANNEL_TEST_PRIORITY;
-        int pid = DEFAULT_CHANNEL_PID;
-        initializeThing(thingConfig, channelID, acceptedItemType, priority, pid);
-    }
-
-    private void initializeThing(Configuration thingConfiguration, String channelID, String acceptedItemType,
-            String priority, int pid) {
-        ThingTypeUID thingTypeUID = SysteminfoBindingConstants.THING_TYPE_COMPUTER;
-        ThingUID thingUID = new ThingUID(thingTypeUID, DEFAULT_TEST_THING_NAME);
-
-        ChannelUID channelUID = new ChannelUID(thingUID, channelID);
-        String channelTypeId = channelUID.getIdWithoutGroup();
-        if ("load1".equals(channelTypeId) || "load5".equals(channelTypeId) || "load15".equals(channelTypeId)) {
-            channelTypeId = "loadAverage";
-        }
-        ChannelTypeUID channelTypeUID = new ChannelTypeUID(SysteminfoBindingConstants.BINDING_ID, channelTypeId);
-        Configuration channelConfig = new Configuration();
-        channelConfig.put("priority", priority);
-        channelConfig.put("pid", new BigDecimal(pid));
-        Channel channel = ChannelBuilder.create(channelUID, acceptedItemType).withType(channelTypeUID)
-                .withKind(ChannelKind.STATE).withConfiguration(channelConfig).build();
-
-        ThingBuilder thingBuilder = ThingBuilder.create(thingTypeUID, thingUID).withConfiguration(thingConfiguration)
-                .withChannel(channel);
-        // Make sure the thingTypeVersion matches the highest version in the update instructions of the binding to avoid
-        // new channels being added and the thing not initializing
-        thingBuilder = thingBuilder.withProperties(Map.of("thingTypeVersion", "1"));
-        Thing thing = thingBuilder.build();
-        systeminfoThing = thing;
-
-        managedThingProvider.add(thing);
-
-        waitForAssert(() -> {
-            SysteminfoHandler handler = (SysteminfoHandler) thing.getHandler();
-            assertThat(handler, is(notNullValue()));
-        });
-
-        waitForAssert(() -> {
-            assertThat("Thing is not initialized, before an Item is created", thing.getStatus(),
-                    anyOf(equalTo(ThingStatus.OFFLINE), equalTo(ThingStatus.ONLINE)));
-        });
-
-        intializeItem(channelUID, DEFAULT_TEST_ITEM_NAME, acceptedItemType);
-    }
-
-    private void assertItemState(String acceptedItemType, String itemName, String priority, State expectedState) {
-        Thing thing = systeminfoThing;
-        if (thing == null) {
-            throw new AssertionError("Thing is null");
-        }
-        waitForAssert(() -> {
-            ThingStatusDetail thingStatusDetail = thing.getStatusInfo().getStatusDetail();
-            String description = thing.getStatusInfo().getDescription();
-            assertThat("Thing status detail is " + thingStatusDetail + " with description " + description,
-                    thing.getStatus(), is(equalTo(ThingStatus.ONLINE)));
-        });
-        // The binding starts all refresh tasks in SysteminfoHandler.scheduleUpdates() after this delay !
-        try {
-            sleep(SysteminfoHandler.WAIT_TIME_CHANNEL_ITEM_LINK_INIT * 1000);
-        } catch (InterruptedException e) {
-            throw new AssertionError("Interrupted while sleeping");
-        }
-
-        GenericItem item;
-        try {
-            item = (GenericItem) itemRegistry.getItem(itemName);
-        } catch (ItemNotFoundException e) {
-            throw new AssertionError("Item not found in registry");
-        }
-
-        int waitTime;
-        if ("High".equals(priority)) {
-            waitTime = DEFAULT_TEST_INTERVAL_HIGH * 1000;
-        } else if ("Medium".equals(priority)) {
-            waitTime = DEFAULT_TEST_INTERVAL_MEDIUM * 1000;
-        } else {
-            waitTime = 100;
-        }
-
-        waitForAssert(() -> {
-            State itemState = item.getState();
-            assertThat(itemState, is(equalTo(expectedState)));
-        }, waitTime, DFL_SLEEP_TIME);
-    }
-
-    private void intializeItem(ChannelUID channelUID, String itemName, String acceptedItemType) {
-        GenericItem item = null;
-        if (acceptedItemType.startsWith("Number")) {
-            item = new NumberItem(acceptedItemType, itemName, unitProvider);
-        } else if ("String".equals(acceptedItemType)) {
-            item = new StringItem(itemName);
-        }
-        if (item == null) {
-            throw new AssertionError("Item is null");
-        }
-        itemRegistry.add(item);
-        testItem = item;
-
-        itemChannelLinkProvider.add(new ItemChannelLink(itemName, channelUID));
-    }
-
-    @Test
-    public void assertInvalidThingConfigurationValuesAreHandled() {
-        Configuration configuration = new Configuration();
-
-        // invalid value - must be positive
-        int refreshIntervalHigh = -5;
-        configuration.put(SysteminfoBindingConstants.HIGH_PRIORITY_REFRESH_TIME, new BigDecimal(refreshIntervalHigh));
-
-        int refreshIntervalMedium = 3;
-        configuration.put(SysteminfoBindingConstants.MEDIUM_PRIORITY_REFRESH_TIME,
-                new BigDecimal(refreshIntervalMedium));
-        initializeThingWithConfiguration(configuration);
-
-        testInvalidConfiguration();
-    }
-
-    private void testInvalidConfiguration() {
-        waitForAssert(() -> {
-            Thing thing = systeminfoThing;
-            if (thing != null) {
-                assertThat("Invalid configuration is used !", thing.getStatus(), is(equalTo(ThingStatus.OFFLINE)));
-                assertThat(thing.getStatusInfo().getStatusDetail(),
-                        is(equalTo(ThingStatusDetail.HANDLER_INITIALIZING_ERROR)));
-                assertThat(thing.getStatusInfo().getDescription(), is(equalTo("@text/offline.cannot-initialize")));
-            }
-        });
-    }
-
-    @Test
-    public void assertMediumPriorityChannelIsUpdated() {
-        String channnelID = DEFAULT_TEST_CHANNEL_ID;
-        String acceptedItemType = "Number";
-        String priority = "Medium";
-
-        initializeThingWithChannelAndPriority(channnelID, acceptedItemType, priority);
-        assertItemState(acceptedItemType, DEFAULT_TEST_ITEM_NAME, priority, UnDefType.UNDEF);
-    }
-
-    @Test
-    public void assertStateOfSecondDeviceIsUpdated() {
-        // This test assumes that at least 2 network interfaces are present on the test platform
-        int deviceIndex = 1;
-        String channnelID = "network" + deviceIndex + "#mac";
-        String acceptedItemType = "String";
-
-        initializeThingWithChannel(channnelID, acceptedItemType);
-        assertItemState(acceptedItemType, DEFAULT_TEST_ITEM_NAME, DEFAULT_CHANNEL_TEST_PRIORITY, UnDefType.UNDEF);
-    }
-
-    @Test
-    public void assertChannelCpuMaxFreq() {
-        String channnelID = SysteminfoBindingConstants.CHANNEL_CPU_MAXFREQ;
-        String acceptedItemType = "Number:Frequency";
-
-        QuantityType<Frequency> mockedCpuMaxFreqValue = new QuantityType<>(2500, Units.HERTZ);
-        when(mockedSystemInfo.getCpuMaxFreq()).thenReturn(mockedCpuMaxFreqValue);
-
-        initializeThingWithChannel(channnelID, acceptedItemType);
-        assertItemState(acceptedItemType, DEFAULT_TEST_ITEM_NAME, DEFAULT_CHANNEL_TEST_PRIORITY, mockedCpuMaxFreqValue);
-    }
-
-    @Test
-    public void assertChannelCpuFreq() {
-        String channnelID = SysteminfoBindingConstants.CHANNEL_CPU_FREQ;
-        String acceptedItemType = "Number:Frequency";
-
-        QuantityType<Frequency> mockedCpuFreqValue = new QuantityType<>(2500, Units.HERTZ);
-        when(mockedSystemInfo.getCpuFreq(0)).thenReturn(mockedCpuFreqValue);
-
-        initializeThingWithChannel(channnelID, acceptedItemType);
-        assertItemState(acceptedItemType, DEFAULT_TEST_ITEM_NAME, DEFAULT_CHANNEL_TEST_PRIORITY, mockedCpuFreqValue);
-    }
-
-    @Test
-    public void assertChannelCpuLoadIsUpdated() {
-        String channnelID = SysteminfoBindingConstants.CHANNEL_CPU_LOAD;
-        String acceptedItemType = "Number";
-
-        PercentType mockedCpuLoadValue = new PercentType(9);
-        when(mockedSystemInfo.getSystemCpuLoad()).thenReturn(mockedCpuLoadValue);
-
-        initializeThingWithChannel(channnelID, acceptedItemType);
-        assertItemState(acceptedItemType, DEFAULT_TEST_ITEM_NAME, DEFAULT_CHANNEL_TEST_PRIORITY, mockedCpuLoadValue);
-    }
-
-    @Test
-    public void assertChannelCpuLoad1IsUpdated() {
-        String channnelID = SysteminfoBindingConstants.CHANNEL_CPU_LOAD_1;
-        String acceptedItemType = "Number";
-
-        DecimalType mockedCpuLoad1Value = new DecimalType(1.1);
-        when(mockedSystemInfo.getCpuLoad1()).thenReturn(mockedCpuLoad1Value);
-
-        initializeThingWithChannel(channnelID, acceptedItemType);
-        assertItemState(acceptedItemType, DEFAULT_TEST_ITEM_NAME, DEFAULT_CHANNEL_TEST_PRIORITY, mockedCpuLoad1Value);
-    }
-
-    @Test
-    public void assertChannelCpuLoad5IsUpdated() {
-        String channnelID = SysteminfoBindingConstants.CHANNEL_CPU_LOAD_5;
-        String acceptedItemType = "Number";
-
-        DecimalType mockedCpuLoad5Value = new DecimalType(5.5);
-        when(mockedSystemInfo.getCpuLoad5()).thenReturn(mockedCpuLoad5Value);
-
-        initializeThingWithChannel(channnelID, acceptedItemType);
-        assertItemState(acceptedItemType, DEFAULT_TEST_ITEM_NAME, DEFAULT_CHANNEL_TEST_PRIORITY, mockedCpuLoad5Value);
-    }
-
-    @Test
-    public void assertChannelCpuLoad15IsUpdated() {
-        String channnelID = SysteminfoBindingConstants.CHANNEL_CPU_LOAD_15;
-        String acceptedItemType = "Number";
-
-        DecimalType mockedCpuLoad15Value = new DecimalType(15.15);
-        when(mockedSystemInfo.getCpuLoad15()).thenReturn(mockedCpuLoad15Value);
-
-        initializeThingWithChannel(channnelID, acceptedItemType);
-        assertItemState(acceptedItemType, DEFAULT_TEST_ITEM_NAME, DEFAULT_CHANNEL_TEST_PRIORITY, mockedCpuLoad15Value);
-    }
-
-    @Test
-    public void assertChannelCpuThreadsIsUpdated() {
-        String channnelID = SysteminfoBindingConstants.CHANNEL_CPU_THREADS;
-        String acceptedItemType = "Number";
-
-        DecimalType mockedCpuThreadsValue = new DecimalType(16);
-        when(mockedSystemInfo.getCpuThreads()).thenReturn(mockedCpuThreadsValue);
-
-        initializeThingWithChannel(channnelID, acceptedItemType);
-        assertItemState(acceptedItemType, DEFAULT_TEST_ITEM_NAME, DEFAULT_CHANNEL_TEST_PRIORITY, mockedCpuThreadsValue);
-    }
-
-    @Test
-    public void assertChannelCpuUptimeIsUpdated() {
-        String channnelID = SysteminfoBindingConstants.CHANNEL_CPU_UPTIME;
-        String acceptedItemType = "Number:Time";
-
-        QuantityType<Time> mockedCpuUptimeValue = new QuantityType<>(100, Units.MINUTE);
-        when(mockedSystemInfo.getCpuUptime()).thenReturn(mockedCpuUptimeValue);
-
-        initializeThingWithChannel(channnelID, acceptedItemType);
-        assertItemState(acceptedItemType, DEFAULT_TEST_ITEM_NAME, DEFAULT_CHANNEL_TEST_PRIORITY, mockedCpuUptimeValue);
-    }
-
-    @Test
-    public void assertChannelCpuDescriptionIsUpdated() {
-        String channnelID = SysteminfoBindingConstants.CHANNEL_CPU_DESCRIPTION;
-        String acceptedItemType = "String";
-
-        StringType mockedCpuDescriptionValue = new StringType("Mocked Cpu Descr");
-        when(mockedSystemInfo.getCpuDescription()).thenReturn(mockedCpuDescriptionValue);
-
-        initializeThingWithChannel(channnelID, acceptedItemType);
-        assertItemState(acceptedItemType, DEFAULT_TEST_ITEM_NAME, DEFAULT_CHANNEL_TEST_PRIORITY,
-                mockedCpuDescriptionValue);
-    }
-
-    @Test
-    public void assertChannelCpuNameIsUpdated() {
-        String channnelID = SysteminfoBindingConstants.CHANNEL_CPU_NAME;
-        String acceptedItemType = "String";
-
-        StringType mockedCpuNameValue = new StringType("Mocked Cpu Name");
-        when(mockedSystemInfo.getCpuName()).thenReturn(mockedCpuNameValue);
-
-        initializeThingWithChannel(channnelID, acceptedItemType);
-        assertItemState(acceptedItemType, DEFAULT_TEST_ITEM_NAME, DEFAULT_CHANNEL_TEST_PRIORITY, mockedCpuNameValue);
-    }
-
-    @Test
-    public void assertChannelMemoryAvailableIsUpdated() {
-        String channnelID = SysteminfoBindingConstants.CHANNEL_MEMORY_AVAILABLE;
-        String acceptedItemType = "Number:DataAmount";
-
-        QuantityType<DataAmount> mockedMemoryAvailableValue = new QuantityType<>(1000, Units.MEBIBYTE);
-        when(mockedSystemInfo.getMemoryAvailable()).thenReturn(mockedMemoryAvailableValue);
-
-        initializeThingWithChannel(channnelID, acceptedItemType);
-        assertItemState(acceptedItemType, DEFAULT_TEST_ITEM_NAME, DEFAULT_CHANNEL_TEST_PRIORITY,
-                mockedMemoryAvailableValue);
-    }
-
-    @Test
-    public void assertChannelMemoryUsedIsUpdated() {
-        String channnelID = SysteminfoBindingConstants.CHANNEL_MEMORY_USED;
-        String acceptedItemType = "Number:DataAmount";
-
-        QuantityType<DataAmount> mockedMemoryUsedValue = new QuantityType<>(24, Units.MEBIBYTE);
-        when(mockedSystemInfo.getMemoryUsed()).thenReturn(mockedMemoryUsedValue);
-
-        initializeThingWithChannel(channnelID, acceptedItemType);
-        assertItemState(acceptedItemType, DEFAULT_TEST_ITEM_NAME, DEFAULT_CHANNEL_TEST_PRIORITY, mockedMemoryUsedValue);
-    }
-
-    @Test
-    public void assertChannelMemoryTotalIsUpdated() {
-        String channnelID = SysteminfoBindingConstants.CHANNEL_MEMORY_TOTAL;
-        String acceptedItemType = "Number:DataAmount";
-
-        QuantityType<DataAmount> mockedMemoryTotalValue = new QuantityType<>(1024, Units.MEBIBYTE);
-        when(mockedSystemInfo.getMemoryTotal()).thenReturn(mockedMemoryTotalValue);
-
-        initializeThingWithChannel(channnelID, acceptedItemType);
-        assertItemState(acceptedItemType, DEFAULT_TEST_ITEM_NAME, DEFAULT_CHANNEL_TEST_PRIORITY,
-                mockedMemoryTotalValue);
-    }
-
-    @Test
-    public void assertChannelMemoryAvailablePercentIsUpdated() {
-        String channnelID = SysteminfoBindingConstants.CHANNEL_MEMORY_AVAILABLE_PERCENT;
-        String acceptedItemType = "Number";
-
-        PercentType mockedMemoryAvailablePercentValue = new PercentType(97);
-        when(mockedSystemInfo.getMemoryAvailablePercent()).thenReturn(mockedMemoryAvailablePercentValue);
-
-        initializeThingWithChannel(channnelID, acceptedItemType);
-        assertItemState(acceptedItemType, DEFAULT_TEST_ITEM_NAME, DEFAULT_CHANNEL_TEST_PRIORITY,
-                mockedMemoryAvailablePercentValue);
-    }
-
-    @Test
-    public void assertChannelSwapAvailableIsUpdated() {
-        String channnelID = SysteminfoBindingConstants.CHANNEL_SWAP_AVAILABLE;
-        String acceptedItemType = "Number:DataAmount";
-
-        QuantityType<DataAmount> mockedSwapAvailableValue = new QuantityType<>(482, Units.MEBIBYTE);
-        when(mockedSystemInfo.getSwapAvailable()).thenReturn(mockedSwapAvailableValue);
-
-        initializeThingWithChannel(channnelID, acceptedItemType);
-        assertItemState(acceptedItemType, DEFAULT_TEST_ITEM_NAME, DEFAULT_CHANNEL_TEST_PRIORITY,
-                mockedSwapAvailableValue);
-    }
-
-    @Test
-    public void assertChannelSwapUsedIsUpdated() {
-        String channnelID = SysteminfoBindingConstants.CHANNEL_SWAP_USED;
-        String acceptedItemType = "Number:DataAmount";
-
-        QuantityType<DataAmount> mockedSwapUsedValue = new QuantityType<>(30, Units.MEBIBYTE);
-        when(mockedSystemInfo.getSwapUsed()).thenReturn(mockedSwapUsedValue);
-
-        initializeThingWithChannel(channnelID, acceptedItemType);
-        assertItemState(acceptedItemType, DEFAULT_TEST_ITEM_NAME, DEFAULT_CHANNEL_TEST_PRIORITY, mockedSwapUsedValue);
-    }
-
-    @Test
-    public void assertChannelSwapTotalIsUpdated() {
-        String channnelID = SysteminfoBindingConstants.CHANNEL_SWAP_TOTAL;
-        String acceptedItemType = "Number:DataAmount";
-
-        QuantityType<DataAmount> mockedSwapTotalValue = new QuantityType<>(512, Units.MEBIBYTE);
-        when(mockedSystemInfo.getSwapTotal()).thenReturn(mockedSwapTotalValue);
-
-        initializeThingWithChannel(channnelID, acceptedItemType);
-        assertItemState(acceptedItemType, DEFAULT_TEST_ITEM_NAME, DEFAULT_CHANNEL_TEST_PRIORITY, mockedSwapTotalValue);
-    }
-
-    @Test
-    public void assertChannelSwapAvailablePercentIsUpdated() {
-        String channnelID = SysteminfoBindingConstants.CHANNEL_SWAP_AVAILABLE_PERCENT;
-        String acceptedItemType = "Number";
-
-        PercentType mockedSwapAvailablePercentValue = new PercentType(94);
-        when(mockedSystemInfo.getSwapAvailablePercent()).thenReturn(mockedSwapAvailablePercentValue);
-
-        initializeThingWithChannel(channnelID, acceptedItemType);
-        assertItemState(acceptedItemType, DEFAULT_TEST_ITEM_NAME, DEFAULT_CHANNEL_TEST_PRIORITY,
-                mockedSwapAvailablePercentValue);
-    }
-
-    @Test
-    public void assertChannelStorageNameIsUpdated() throws DeviceNotFoundException {
-        String channnelID = SysteminfoBindingConstants.CHANNEL_STORAGE_NAME;
-        String acceptedItemType = "String";
-
-        StringType mockedStorageName = new StringType("Mocked Storage Name");
-        when(mockedSystemInfo.getStorageName(DEFAULT_DEVICE_INDEX)).thenReturn(mockedStorageName);
-
-        initializeThingWithChannel(channnelID, acceptedItemType);
-        assertItemState(acceptedItemType, DEFAULT_TEST_ITEM_NAME, DEFAULT_CHANNEL_TEST_PRIORITY, mockedStorageName);
-    }
-
-    @Test
-    public void assertChannelStorageTypeIsUpdated() throws DeviceNotFoundException {
-        String channnelID = SysteminfoBindingConstants.CHANNEL_STORAGE_TYPE;
-        String acceptedItemType = "String";
-
-        StringType mockedStorageType = new StringType("Mocked Storage Type");
-        when(mockedSystemInfo.getStorageType(DEFAULT_DEVICE_INDEX)).thenReturn(mockedStorageType);
-
-        initializeThingWithChannel(channnelID, acceptedItemType);
-        assertItemState(acceptedItemType, DEFAULT_TEST_ITEM_NAME, DEFAULT_CHANNEL_TEST_PRIORITY, mockedStorageType);
-    }
-
-    @Test
-    public void assertChannelStorageDescriptionIsUpdated() throws DeviceNotFoundException {
-        String channnelID = SysteminfoBindingConstants.CHANNEL_STORAGE_DESCRIPTION;
-        String acceptedItemType = "String";
-
-        StringType mockedStorageDescription = new StringType("Mocked Storage Description");
-        when(mockedSystemInfo.getStorageDescription(DEFAULT_DEVICE_INDEX)).thenReturn(mockedStorageDescription);
-
-        initializeThingWithChannel(channnelID, acceptedItemType);
-        assertItemState(acceptedItemType, DEFAULT_TEST_ITEM_NAME, DEFAULT_CHANNEL_TEST_PRIORITY,
-                mockedStorageDescription);
-    }
-
-    @Test
-    public void assertChannelStorageAvailableIsUpdated() throws DeviceNotFoundException {
-        String channnelID = SysteminfoBindingConstants.CHANNEL_STORAGE_AVAILABLE;
-        String acceptedItemType = "Number:DataAmount";
-
-        QuantityType<DataAmount> mockedStorageAvailableValue = new QuantityType<>(2000, Units.MEBIBYTE);
-        when(mockedSystemInfo.getStorageAvailable(DEFAULT_DEVICE_INDEX)).thenReturn(mockedStorageAvailableValue);
-
-        initializeThingWithChannel(channnelID, acceptedItemType);
-        assertItemState(acceptedItemType, DEFAULT_TEST_ITEM_NAME, DEFAULT_CHANNEL_TEST_PRIORITY,
-                mockedStorageAvailableValue);
-    }
-
-    @Test
-    public void assertChannelStorageUsedIsUpdated() throws DeviceNotFoundException {
-        String channnelID = SysteminfoBindingConstants.CHANNEL_STORAGE_USED;
-        String acceptedItemType = "Number:DataAmount";
-
-        QuantityType<DataAmount> mockedStorageUsedValue = new QuantityType<>(500, Units.MEBIBYTE);
-        when(mockedSystemInfo.getStorageUsed(DEFAULT_DEVICE_INDEX)).thenReturn(mockedStorageUsedValue);
-
-        initializeThingWithChannel(channnelID, acceptedItemType);
-        assertItemState(acceptedItemType, DEFAULT_TEST_ITEM_NAME, DEFAULT_CHANNEL_TEST_PRIORITY,
-                mockedStorageUsedValue);
-    }
-
-    @Test
-    public void assertChannelStorageTotalIsUpdated() throws DeviceNotFoundException {
-        String channnelID = SysteminfoBindingConstants.CHANNEL_STORAGE_TOTAL;
-        String acceptedItemType = "Number:DataAmount";
-
-        QuantityType<DataAmount> mockedStorageTotalValue = new QuantityType<>(2500, Units.MEBIBYTE);
-        when(mockedSystemInfo.getStorageTotal(DEFAULT_DEVICE_INDEX)).thenReturn(mockedStorageTotalValue);
-
-        initializeThingWithChannel(channnelID, acceptedItemType);
-        assertItemState(acceptedItemType, DEFAULT_TEST_ITEM_NAME, DEFAULT_CHANNEL_TEST_PRIORITY,
-                mockedStorageTotalValue);
-    }
-
-    @Test
-    public void assertChannelStorageAvailablePercentIsUpdated() throws DeviceNotFoundException {
-        String channnelID = SysteminfoBindingConstants.CHANNEL_STORAGE_AVAILABLE_PERCENT;
-        String acceptedItemType = "Number";
-
-        PercentType mockedStorageAvailablePercent = new PercentType(20);
-        when(mockedSystemInfo.getStorageAvailablePercent(DEFAULT_DEVICE_INDEX))
-                .thenReturn(mockedStorageAvailablePercent);
-
-        initializeThingWithChannel(channnelID, acceptedItemType);
-        assertItemState(acceptedItemType, DEFAULT_TEST_ITEM_NAME, DEFAULT_CHANNEL_TEST_PRIORITY,
-                mockedStorageAvailablePercent);
-    }
-
-    @Test
-    public void assertChannelDriveNameIsUpdated() throws DeviceNotFoundException {
-        String channelID = SysteminfoBindingConstants.CHANNEL_DRIVE_NAME;
-        String acceptedItemType = "String";
-
-        StringType mockedDriveNameValue = new StringType("Mocked Drive Name");
-        when(mockedSystemInfo.getDriveName(DEFAULT_DEVICE_INDEX)).thenReturn(mockedDriveNameValue);
-
-        initializeThingWithChannel(channelID, acceptedItemType);
-        assertItemState(acceptedItemType, DEFAULT_TEST_ITEM_NAME, DEFAULT_CHANNEL_TEST_PRIORITY, mockedDriveNameValue);
-    }
-
-    @Test
-    public void assertChannelDriveModelIsUpdated() throws DeviceNotFoundException {
-        String channelID = SysteminfoBindingConstants.CHANNEL_DRIVE_MODEL;
-        String acceptedItemType = "String";
-
-        StringType mockedDriveModelValue = new StringType("Mocked Drive Model");
-        when(mockedSystemInfo.getDriveModel(DEFAULT_DEVICE_INDEX)).thenReturn(mockedDriveModelValue);
-
-        initializeThingWithChannel(channelID, acceptedItemType);
-        assertItemState(acceptedItemType, DEFAULT_TEST_ITEM_NAME, DEFAULT_CHANNEL_TEST_PRIORITY, mockedDriveModelValue);
-    }
-
-    @Test
-    public void assertChannelDriveSerialIsUpdated() throws DeviceNotFoundException {
-        String channelID = SysteminfoBindingConstants.CHANNEL_DRIVE_SERIAL;
-        String acceptedItemType = "String";
-
-        StringType mockedDriveSerialNumber = new StringType("Mocked Drive Serial Number");
-        when(mockedSystemInfo.getDriveSerialNumber(DEFAULT_DEVICE_INDEX)).thenReturn(mockedDriveSerialNumber);
-
-        initializeThingWithChannel(channelID, acceptedItemType);
-        assertItemState(acceptedItemType, DEFAULT_TEST_ITEM_NAME, DEFAULT_CHANNEL_TEST_PRIORITY,
-                mockedDriveSerialNumber);
-    }
-
-    // Re-enable this previously disabled test, as it is not relying on hardware anymore, but a mocked object
-    // There is a bug opened for this issue - https://github.com/dblock/oshi/issues/185
-    @Test
-    public void assertChannelSensorsCpuTempIsUpdated() {
-        String channnelID = SysteminfoBindingConstants.CHANNEL_SENSORS_CPU_TEMPERATURE;
-        String acceptedItemType = "Number:Temperature";
-
-        QuantityType<Temperature> mockedSensorsCpuTemperatureValue = new QuantityType<>(60, SIUnits.CELSIUS);
-        when(mockedSystemInfo.getSensorsCpuTemperature()).thenReturn(mockedSensorsCpuTemperatureValue);
-
-        initializeThingWithChannel(channnelID, acceptedItemType);
-        assertItemState(acceptedItemType, DEFAULT_TEST_ITEM_NAME, DEFAULT_CHANNEL_TEST_PRIORITY,
-                mockedSensorsCpuTemperatureValue);
-    }
-
-    @Test
-    public void assertChannelSensorsCpuVoltageIsUpdated() {
-        String channnelID = SysteminfoBindingConstants.CHANNEL_SENOSRS_CPU_VOLTAGE;
-        String acceptedItemType = "Number:ElectricPotential";
-
-        QuantityType<ElectricPotential> mockedSensorsCpuVoltageValue = new QuantityType<>(1000, Units.VOLT);
-        when(mockedSystemInfo.getSensorsCpuVoltage()).thenReturn(mockedSensorsCpuVoltageValue);
-
-        initializeThingWithChannel(channnelID, acceptedItemType);
-        assertItemState(acceptedItemType, DEFAULT_TEST_ITEM_NAME, DEFAULT_CHANNEL_TEST_PRIORITY,
-                mockedSensorsCpuVoltageValue);
-    }
-
-    @Test
-    public void assertChannelSensorsFanSpeedIsUpdated() throws DeviceNotFoundException {
-        String channnelID = SysteminfoBindingConstants.CHANNEL_SENSORS_FAN_SPEED;
-        String acceptedItemType = "Number";
-
-        DecimalType mockedSensorsCpuFanSpeedValue = new DecimalType(180);
-        when(mockedSystemInfo.getSensorsFanSpeed(DEFAULT_DEVICE_INDEX)).thenReturn(mockedSensorsCpuFanSpeedValue);
-
-        initializeThingWithChannel(channnelID, acceptedItemType);
-        assertItemState(acceptedItemType, DEFAULT_TEST_ITEM_NAME, DEFAULT_CHANNEL_TEST_PRIORITY,
-                mockedSensorsCpuFanSpeedValue);
-    }
-
-    @Test
-    public void assertChannelBatteryNameIsUpdated() throws DeviceNotFoundException {
-        String channnelID = SysteminfoBindingConstants.CHANNEL_BATTERY_NAME;
-        String acceptedItemType = "String";
-
-        StringType mockedBatteryName = new StringType("Mocked Battery Name");
-        when(mockedSystemInfo.getBatteryName(DEFAULT_DEVICE_INDEX)).thenReturn(mockedBatteryName);
-
-        initializeThingWithChannel(channnelID, acceptedItemType);
-        assertItemState(acceptedItemType, DEFAULT_TEST_ITEM_NAME, DEFAULT_CHANNEL_TEST_PRIORITY, mockedBatteryName);
-    }
-
-    @Test
-    public void assertChannelBatteryRemainingCapacityIsUpdated() throws DeviceNotFoundException {
-        String channnelID = SysteminfoBindingConstants.CHANNEL_BATTERY_REMAINING_CAPACITY;
-        String acceptedItemType = "Number";
-
-        PercentType mockedBatteryRemainingCapacity = new PercentType(20);
-        when(mockedSystemInfo.getBatteryRemainingCapacity(DEFAULT_DEVICE_INDEX))
-                .thenReturn(mockedBatteryRemainingCapacity);
-
-        initializeThingWithChannel(channnelID, acceptedItemType);
-        assertItemState(acceptedItemType, DEFAULT_TEST_ITEM_NAME, DEFAULT_CHANNEL_TEST_PRIORITY,
-                mockedBatteryRemainingCapacity);
-    }
-
-    @Test
-    public void assertChannelBatteryRemainingTimeIsUpdated() throws DeviceNotFoundException {
-        String channnelID = SysteminfoBindingConstants.CHANNEL_BATTERY_REMAINING_TIME;
-        String acceptedItemType = "Number:Time";
-
-        QuantityType<Time> mockedBatteryRemainingTime = new QuantityType<>(3600, Units.MINUTE);
-        when(mockedSystemInfo.getBatteryRemainingTime(DEFAULT_DEVICE_INDEX)).thenReturn(mockedBatteryRemainingTime);
-
-        initializeThingWithChannel(channnelID, acceptedItemType);
-        assertItemState(acceptedItemType, DEFAULT_TEST_ITEM_NAME, DEFAULT_CHANNEL_TEST_PRIORITY,
-                mockedBatteryRemainingTime);
-    }
-
-    @Test
-    public void assertChannelDisplayInformationIsUpdated() throws DeviceNotFoundException {
-        String channnelID = SysteminfoBindingConstants.CHANNEL_DISPLAY_INFORMATION;
-        String acceptedItemType = "String";
-
-        StringType mockedDisplayInfo = new StringType("Mocked Display Information");
-        when(mockedSystemInfo.getDisplayInformation(DEFAULT_DEVICE_INDEX)).thenReturn(mockedDisplayInfo);
-
-        initializeThingWithChannel(channnelID, acceptedItemType);
-        assertItemState(acceptedItemType, DEFAULT_TEST_ITEM_NAME, DEFAULT_CHANNEL_TEST_PRIORITY, mockedDisplayInfo);
-    }
-
-    @Test
-    public void assertChannelNetworkIpIsUpdated() throws DeviceNotFoundException {
-        String channnelID = SysteminfoBindingConstants.CHANNEL_NETWORK_IP;
-        String acceptedItemType = "String";
-
-        StringType mockedNetworkIp = new StringType("192.168.1.0");
-        when(mockedSystemInfo.getNetworkIp(DEFAULT_DEVICE_INDEX)).thenReturn(mockedNetworkIp);
-
-        initializeThingWithChannel(channnelID, acceptedItemType);
-        assertItemState(acceptedItemType, DEFAULT_TEST_ITEM_NAME, DEFAULT_CHANNEL_TEST_PRIORITY, mockedNetworkIp);
-    }
-
-    @Test
-    public void assertChannelNetworkMacIsUpdated() throws DeviceNotFoundException {
-        String channnelID = SysteminfoBindingConstants.CHANNEL_NETWORK_MAC;
-        String acceptedItemType = "String";
-
-        StringType mockedNetworkMacValue = new StringType("AB-10-11-12-13-14");
-        when(mockedSystemInfo.getNetworkMac(DEFAULT_DEVICE_INDEX)).thenReturn(mockedNetworkMacValue);
-
-        initializeThingWithChannel(channnelID, acceptedItemType);
-        assertItemState(acceptedItemType, DEFAULT_TEST_ITEM_NAME, DEFAULT_CHANNEL_TEST_PRIORITY, mockedNetworkMacValue);
-    }
-
-    @Test
-    public void assertChannelNetworkDataSentIsUpdated() throws DeviceNotFoundException {
-        String channnelID = SysteminfoBindingConstants.CHANNEL_NETWORK_DATA_SENT;
-        String acceptedItemType = "Number:DataAmount";
-
-        QuantityType<DataAmount> mockedNetworkDataSent = new QuantityType<>(1000, Units.MEBIBYTE);
-        when(mockedSystemInfo.getNetworkDataSent(DEFAULT_DEVICE_INDEX)).thenReturn(mockedNetworkDataSent);
-
-        initializeThingWithChannel(channnelID, acceptedItemType);
-        assertItemState(acceptedItemType, DEFAULT_TEST_ITEM_NAME, DEFAULT_CHANNEL_TEST_PRIORITY, mockedNetworkDataSent);
-    }
-
-    @Test
-    public void assertChannelNetworkDataReceivedIsUpdated() throws DeviceNotFoundException {
-        String channnelID = SysteminfoBindingConstants.CHANNEL_NETWORK_DATA_RECEIVED;
-        String acceptedItemType = "Number:DataAmount";
-
-        QuantityType<DataAmount> mockedNetworkDataReceiveed = new QuantityType<>(800, Units.MEBIBYTE);
-        when(mockedSystemInfo.getNetworkDataReceived(DEFAULT_DEVICE_INDEX)).thenReturn(mockedNetworkDataReceiveed);
-
-        initializeThingWithChannel(channnelID, acceptedItemType);
-        assertItemState(acceptedItemType, DEFAULT_TEST_ITEM_NAME, DEFAULT_CHANNEL_TEST_PRIORITY,
-                mockedNetworkDataReceiveed);
-    }
-
-    @Test
-    public void assertChannelNetworkPacketsSentIsUpdated() throws DeviceNotFoundException {
-        String channnelID = SysteminfoBindingConstants.CHANNEL_NETWORK_PACKETS_SENT;
-        String acceptedItemType = "Number";
-
-        DecimalType mockedNetworkPacketsSent = new DecimalType(50);
-        when(mockedSystemInfo.getNetworkPacketsSent(DEFAULT_DEVICE_INDEX)).thenReturn(mockedNetworkPacketsSent);
-
-        initializeThingWithChannel(channnelID, acceptedItemType);
-        assertItemState(acceptedItemType, DEFAULT_TEST_ITEM_NAME, DEFAULT_CHANNEL_TEST_PRIORITY,
-                mockedNetworkPacketsSent);
-    }
-
-    @Test
-    public void assertChannelNetworkPacketsReceivedIsUpdated() throws DeviceNotFoundException {
-        String channnelID = SysteminfoBindingConstants.CHANNEL_NETWORK_PACKETS_RECEIVED;
-        String acceptedItemType = "Number";
-
-        DecimalType mockedNetworkPacketsReceived = new DecimalType(48);
-        when(mockedSystemInfo.getNetworkPacketsReceived(DEFAULT_DEVICE_INDEX)).thenReturn(mockedNetworkPacketsReceived);
-
-        initializeThingWithChannel(channnelID, acceptedItemType);
-        assertItemState(acceptedItemType, DEFAULT_TEST_ITEM_NAME, DEFAULT_CHANNEL_TEST_PRIORITY,
-                mockedNetworkPacketsReceived);
-    }
-
-    @Test
-    public void assertChannelNetworkNetworkNameIsUpdated() throws DeviceNotFoundException {
-        String channnelID = SysteminfoBindingConstants.CHANNEL_NETWORK_NAME;
-        String acceptedItemType = "String";
-
-        StringType mockedNetworkName = new StringType("MockN-AQ34");
-        when(mockedSystemInfo.getNetworkName(DEFAULT_DEVICE_INDEX)).thenReturn(mockedNetworkName);
-
-        initializeThingWithChannel(channnelID, acceptedItemType);
-        assertItemState(acceptedItemType, DEFAULT_TEST_ITEM_NAME, DEFAULT_CHANNEL_TEST_PRIORITY, mockedNetworkName);
-    }
-
-    @Test
-    public void assertChannelNetworkNetworkDisplayNameIsUpdated() throws DeviceNotFoundException {
-        String channnelID = SysteminfoBindingConstants.CHANNEL_NETWORK_ADAPTER_NAME;
-        String acceptedItemType = "String";
-
-        StringType mockedNetworkAdapterName = new StringType("Mocked Network Adapter Name");
-        when(mockedSystemInfo.getNetworkDisplayName(DEFAULT_DEVICE_INDEX)).thenReturn(mockedNetworkAdapterName);
-
-        initializeThingWithChannel(channnelID, acceptedItemType);
-        assertItemState(acceptedItemType, DEFAULT_TEST_ITEM_NAME, DEFAULT_CHANNEL_TEST_PRIORITY,
-                mockedNetworkAdapterName);
-    }
-
-    class SysteminfoDiscoveryServiceMock extends SysteminfoDiscoveryService {
-        String hostname;
-
-        SysteminfoDiscoveryServiceMock(String hostname) {
-            super();
-            this.hostname = hostname;
-        }
-
-        @Override
-        protected String getHostName() throws UnknownHostException {
-            if ("unresolved".equals(hostname)) {
-                throw new UnknownHostException();
-            }
-            return hostname;
-        }
-
-        @Override
-        public void startScan() {
-            super.startScan();
-        }
-    }
-
-    @Test
-    public void testDiscoveryWithInvalidHostname() {
-        String hostname = "Hilo.fritz.box";
-        String expectedHostname = "Hilo_fritz_box";
-
-        testDiscoveryService(expectedHostname, hostname);
-    }
-
-    @Test
-    public void testDiscoveryWithValidHostname() {
-        String hostname = "MyComputer";
-        String expectedHostname = "MyComputer";
-
-        testDiscoveryService(expectedHostname, hostname);
-    }
-
-    @Test
-    public void testDiscoveryWithUnresolvedHostname() {
-        String hostname = "unresolved";
-        String expectedHostname = SysteminfoDiscoveryService.DEFAULT_THING_ID;
-
-        testDiscoveryService(expectedHostname, hostname);
-    }
-
-    @Test
-    public void testDiscoveryWithEmptyHostnameString() {
-        String hostname = "";
-        String expectedHostname = SysteminfoDiscoveryService.DEFAULT_THING_ID;
-
-        testDiscoveryService(expectedHostname, hostname);
-    }
-
-    private void testDiscoveryService(String expectedHostname, String hostname) {
-        SysteminfoDiscoveryService discoveryService = getService(DiscoveryService.class,
-                SysteminfoDiscoveryService.class);
-        waitForAssert(() -> {
-            assertThat(discoveryService, is(notNullValue()));
-        });
-        SysteminfoDiscoveryServiceMock discoveryServiceMock = new SysteminfoDiscoveryServiceMock(hostname);
-        if (discoveryService != null) {
-            unregisterService(DiscoveryService.class);
-        }
-        registerService(discoveryServiceMock, DiscoveryService.class.getName(), new Hashtable<>());
-
-        ThingTypeUID computerType = SysteminfoBindingConstants.THING_TYPE_COMPUTER;
-        ThingUID computerUID = new ThingUID(computerType, expectedHostname);
-
-        discoveryServiceMock.startScan();
-
-        Inbox inbox = getService(Inbox.class);
-        waitForAssert(() -> {
-            assertThat(inbox, is(notNullValue()));
-        });
-
-        if (inbox == null) {
-            return;
-        }
-
-        waitForAssert(() -> {
-            List<DiscoveryResult> results = inbox.stream().filter(InboxPredicates.forThingUID(computerUID)).toList();
-            assertFalse(results.isEmpty(), "No Thing with UID " + computerUID.getAsString() + " in inbox");
-        });
-
-        inbox.approve(computerUID, SysteminfoDiscoveryService.DEFAULT_THING_LABEL, null);
-
-        waitForAssert(() -> {
-            systeminfoThing = thingRegistry.get(computerUID);
-            assertThat(systeminfoThing, is(notNullValue()));
-        });
-
-        Thing thing = systeminfoThing;
-        if (thing == null) {
-            return;
-        }
-
-        waitForAssert(() -> {
-            assertThat("Thing is not initialized.", thing.getStatus(), is(equalTo(ThingStatus.ONLINE)));
-        });
-    }
-
-    @Test
-    public void assertChannelProcessThreadsIsUpdatedWithPIDse() throws DeviceNotFoundException {
-        String channnelID = SysteminfoBindingConstants.CHANNEL_PROCESS_THREADS;
-        String acceptedItemType = "Number";
-        // The pid of the System idle process in Windows
-        int pid = 0;
-
-        DecimalType mockedProcessThreadsCount = new DecimalType(4);
-        when(mockedSystemInfo.getProcessThreads(pid)).thenReturn(mockedProcessThreadsCount);
-
-        initializeThingWithChannelAndPID(channnelID, acceptedItemType, pid);
-        assertItemState(acceptedItemType, DEFAULT_TEST_ITEM_NAME, DEFAULT_CHANNEL_TEST_PRIORITY,
-                mockedProcessThreadsCount);
-    }
-
-    @Test
-    public void assertChannelProcessPathIsUpdatedWithPIDset() throws DeviceNotFoundException {
-        String channnelID = SysteminfoBindingConstants.CHANNEL_PROCESS_PATH;
-        String acceptedItemType = "String";
-        // The pid of the System idle process in Windows
-        int pid = 0;
-
-        StringType mockedProcessPath = new StringType("C:\\Users\\MockedUser\\Process");
-        when(mockedSystemInfo.getProcessPath(pid)).thenReturn(mockedProcessPath);
-
-        initializeThingWithChannelAndPID(channnelID, acceptedItemType, pid);
-        assertItemState(acceptedItemType, DEFAULT_TEST_ITEM_NAME, DEFAULT_CHANNEL_TEST_PRIORITY, mockedProcessPath);
-    }
-
-    @Test
-    public void assertChannelProcessNameIsUpdatedWithPIDset() throws DeviceNotFoundException {
-        String channnelID = SysteminfoBindingConstants.CHANNEL_PROCESS_NAME;
-        String acceptedItemType = "String";
-        // The pid of the System idle process in Windows
-        int pid = 0;
-
-        StringType mockedProcessName = new StringType("MockedProcess.exe");
-        when(mockedSystemInfo.getProcessName(pid)).thenReturn(mockedProcessName);
-
-        initializeThingWithChannelAndPID(channnelID, acceptedItemType, pid);
-        assertItemState(acceptedItemType, DEFAULT_TEST_ITEM_NAME, DEFAULT_CHANNEL_TEST_PRIORITY, mockedProcessName);
-    }
-
-    @Test
-    public void assertChannelProcessMemoryIsUpdatedWithPIDset() throws DeviceNotFoundException {
-        String channnelID = SysteminfoBindingConstants.CHANNEL_PROCESS_MEMORY;
-        String acceptedItemType = "Number:DataAmount";
-        // The pid of the System idle process in Windows
-        int pid = 0;
-
-        QuantityType<DataAmount> mockedProcessMemory = new QuantityType<>(450, Units.MEBIBYTE);
-        when(mockedSystemInfo.getProcessMemoryUsage(pid)).thenReturn(mockedProcessMemory);
-
-        initializeThingWithChannelAndPID(channnelID, acceptedItemType, pid);
-        assertItemState(acceptedItemType, DEFAULT_TEST_ITEM_NAME, DEFAULT_CHANNEL_TEST_PRIORITY, mockedProcessMemory);
-    }
-
-    @Test
-    public void assertChannelProcessLoadIsUpdatedWithPIDset() throws DeviceNotFoundException {
-        String channnelID = SysteminfoBindingConstants.CHANNEL_PROCESS_LOAD;
-        String acceptedItemType = "Number";
-        // The pid of the System idle process in Windows
-        int pid = 0;
-
-        DecimalType mockedProcessLoad = new DecimalType(3);
-        when(mockedSystemInfo.getProcessCpuUsage(pid)).thenReturn(mockedProcessLoad);
-
-        initializeThingWithChannelAndPID(channnelID, acceptedItemType, pid);
-        assertItemState(acceptedItemType, DEFAULT_TEST_ITEM_NAME, DEFAULT_CHANNEL_TEST_PRIORITY, mockedProcessLoad);
-    }
-
-    @Test
-    public void testThingHandlesChannelPriorityChange() {
-        String priorityKey = "priority";
-        String pidKey = "pid";
-        String initialPriority = DEFAULT_CHANNEL_TEST_PRIORITY; // Evaluates to High
-        String newPriority = "Low";
-
-        String acceptedItemType = "Number";
-        initializeThingWithChannel(DEFAULT_TEST_CHANNEL_ID, acceptedItemType);
-
-        Thing thing = systeminfoThing;
-        if (thing == null) {
-            throw new AssertionError("Thing is null");
-        }
-        Channel channel = thing.getChannel(DEFAULT_TEST_CHANNEL_ID);
-        if (channel == null) {
-            throw new AssertionError("Channel '" + DEFAULT_TEST_CHANNEL_ID + "' is null");
-        }
-
-        ThingHandler thingHandler = thing.getHandler();
-        if (thingHandler == null) {
-            throw new AssertionError("Thing handler is null");
-        }
-        if (!(thingHandler.getClass().equals(SysteminfoHandler.class))) {
-            throw new AssertionError("Thing handler not of class SysteminfoHandler");
-        }
-        SysteminfoHandler handler = (SysteminfoHandler) thingHandler;
-        waitForAssert(() -> {
-            assertThat("The initial priority of channel " + channel.getUID() + " is not as expected.",
-                    channel.getConfiguration().get(priorityKey), is(equalTo(initialPriority)));
-            assertThat(handler.getHighPriorityChannels().contains(channel.getUID()), is(true));
-        });
-
-        // Change the priority of a channel, keep the pid
-        Configuration updatedConfig = new Configuration();
-        updatedConfig.put(priorityKey, newPriority);
-        updatedConfig.put(pidKey, channel.getConfiguration().get(pidKey));
-        Channel updatedChannel = ChannelBuilder.create(channel.getUID(), channel.getAcceptedItemType())
-                .withType(channel.getChannelTypeUID()).withKind(channel.getKind()).withConfiguration(updatedConfig)
-                .build();
-
-        Thing updatedThing = ThingBuilder.create(thing.getThingTypeUID(), thing.getUID())
-                .withConfiguration(thing.getConfiguration()).withChannel(updatedChannel).build();
-
-        handler.thingUpdated(updatedThing);
-
-        waitForAssert(() -> {
-            assertThat("The prority of the channel was not updated: ", channel.getConfiguration().get(priorityKey),
-                    is(equalTo(newPriority)));
-            assertThat(handler.getLowPriorityChannels().contains(channel.getUID()), is(true));
-        });
-    }
-}