]> git.basschouten.com Git - openhab-addons.git/blob
5dced54d95d6191ab22c3907cab9e4fd33d96f5f
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
7  * This program and the accompanying materials are made available under the
8  * terms of the Eclipse Public License 2.0 which is available at
9  * http://www.eclipse.org/legal/epl-2.0
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.systeminfo.internal.handler;
14
15 import static org.openhab.binding.systeminfo.internal.SysteminfoBindingConstants.*;
16
17 import java.math.BigDecimal;
18 import java.util.ArrayList;
19 import java.util.Collections;
20 import java.util.HashSet;
21 import java.util.Iterator;
22 import java.util.List;
23 import java.util.Map;
24 import java.util.Set;
25 import java.util.concurrent.ScheduledFuture;
26 import java.util.concurrent.TimeUnit;
27 import java.util.stream.Collectors;
28
29 import org.eclipse.jdt.annotation.NonNullByDefault;
30 import org.eclipse.jdt.annotation.Nullable;
31 import org.openhab.binding.systeminfo.internal.SysteminfoThingTypeProvider;
32 import org.openhab.binding.systeminfo.internal.model.DeviceNotFoundException;
33 import org.openhab.binding.systeminfo.internal.model.SysteminfoInterface;
34 import org.openhab.core.cache.ExpiringCache;
35 import org.openhab.core.cache.ExpiringCacheMap;
36 import org.openhab.core.config.core.Configuration;
37 import org.openhab.core.library.types.DecimalType;
38 import org.openhab.core.library.types.PercentType;
39 import org.openhab.core.library.types.QuantityType;
40 import org.openhab.core.library.unit.Units;
41 import org.openhab.core.thing.Channel;
42 import org.openhab.core.thing.ChannelUID;
43 import org.openhab.core.thing.Thing;
44 import org.openhab.core.thing.ThingStatus;
45 import org.openhab.core.thing.ThingStatusDetail;
46 import org.openhab.core.thing.ThingTypeUID;
47 import org.openhab.core.thing.ThingUID;
48 import org.openhab.core.thing.binding.BaseThingHandler;
49 import org.openhab.core.thing.binding.builder.ThingBuilder;
50 import org.openhab.core.thing.type.ChannelGroupDefinition;
51 import org.openhab.core.types.Command;
52 import org.openhab.core.types.RefreshType;
53 import org.openhab.core.types.State;
54 import org.openhab.core.types.UnDefType;
55 import org.slf4j.Logger;
56 import org.slf4j.LoggerFactory;
57
58 /**
59  * The {@link SysteminfoHandler} is responsible for providing real time information about the system
60  * (CPU, Memory, Storage, Display and others).
61  *
62  * @author Svilen Valkanov - Initial contribution
63  * @author Lyubomir Papzov - Separate the creation of the systeminfo object and its initialization
64  * @author Wouter Born - Add null annotations
65  * @author Mark Herwege - Add dynamic creation of extra channels
66  */
67 @NonNullByDefault
68 public class SysteminfoHandler extends BaseThingHandler {
69     /**
70      * Refresh interval for {@link #highPriorityChannels} in seconds.
71      */
72     private @NonNullByDefault({}) BigDecimal refreshIntervalHighPriority;
73
74     /**
75      * Refresh interval for {@link #mediumPriorityChannels} in seconds.
76      */
77     private @NonNullByDefault({}) BigDecimal refreshIntervalMediumPriority;
78
79     /**
80      * Channels with priority configuration parameter set to High. They usually need frequent update of the state like
81      * CPU load, or information about the free and used memory.
82      * They are updated periodically at {@link #refreshIntervalHighPriority}.
83      */
84     private final Set<ChannelUID> highPriorityChannels = new HashSet<>();
85
86     /**
87      * Channels with priority configuration parameter set to Medium. These channels usually need update of the
88      * state not so oft like battery capacity, storage used and etc.
89      * They are updated periodically at {@link #refreshIntervalMediumPriority}.
90      */
91     private final Set<ChannelUID> mediumPriorityChannels = new HashSet<>();
92
93     /**
94      * Channels with priority configuration parameter set to Low. They represent static information or information
95      * that is updated rare- e.g. CPU name, storage name and etc.
96      * They are updated only at {@link #initialize()}.
97      */
98     private final Set<ChannelUID> lowPriorityChannels = new HashSet<>();
99
100     /**
101      * Wait time for the creation of Item-Channel links in seconds. This delay is needed, because the Item-Channel
102      * links have to be created before the thing state is updated, otherwise item state will not be updated.
103      */
104     public static final int WAIT_TIME_CHANNEL_ITEM_LINK_INIT = 1;
105
106     /**
107      * String used to extend thingUID and channelGroupTypeUID for thing definition with added dynamic channels and
108      * extended channels. It is set in the constructor and unique to the thing.
109      */
110     public final String idExtString;
111
112     public final SysteminfoThingTypeProvider thingTypeProvider;
113
114     private SysteminfoInterface systeminfo;
115
116     private @Nullable ScheduledFuture<?> highPriorityTasks;
117     private @Nullable ScheduledFuture<?> mediumPriorityTasks;
118
119     /**
120      * Caches for cpu process load and process load for a given pid. Using this cache limits the process load refresh
121      * interval to the minimum interval. Too frequent refreshes leads to inaccurate results. This could happen when the
122      * same process is tracked as current process and as a channel with pid parameter, or when the task interval is set
123      * too low.
124      */
125     private static final int MIN_PROCESS_LOAD_REFRESH_INTERVAL_MS = 2000;
126     private ExpiringCache<PercentType> cpuLoadCache = new ExpiringCache<>(MIN_PROCESS_LOAD_REFRESH_INTERVAL_MS,
127             () -> getSystemCpuLoad());
128     private ExpiringCacheMap<Integer, @Nullable DecimalType> processLoadCache = new ExpiringCacheMap<>(
129             MIN_PROCESS_LOAD_REFRESH_INTERVAL_MS);
130
131     private final Logger logger = LoggerFactory.getLogger(SysteminfoHandler.class);
132
133     public SysteminfoHandler(Thing thing, SysteminfoThingTypeProvider thingTypeProvider,
134             SysteminfoInterface systeminfo) {
135         super(thing);
136         this.thingTypeProvider = thingTypeProvider;
137         this.systeminfo = systeminfo;
138
139         idExtString = "-" + thing.getUID().getId();
140     }
141
142     @Override
143     public void initialize() {
144         logger.trace("Initializing thing {} with thing type {}", thing.getUID().getId(),
145                 thing.getThingTypeUID().getId());
146         restoreChannelsConfig(); // After a thing type change, previous channel configs will have been stored, and will
147                                  // be restored here.
148         if (instantiateSysteminfoLibrary() && isConfigurationValid() && updateProperties()) {
149             if (!addDynamicChannels()) { // If there are new channel groups, the thing will get recreated with a new
150                                          // thing type and this handler will be disposed. Therefore do not do anything
151                                          // further here.
152                 groupChannelsByPriority();
153                 scheduleUpdates();
154                 updateStatus(ThingStatus.ONLINE);
155             }
156         } else {
157             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.HANDLER_INITIALIZING_ERROR,
158                     "@text/offline.cannot-initialize");
159         }
160     }
161
162     private boolean instantiateSysteminfoLibrary() {
163         try {
164             systeminfo.initializeSysteminfo();
165             logger.debug("Systeminfo implementation is instantiated!");
166             return true;
167         } catch (Exception e) {
168             logger.warn("Cannot instantiate Systeminfo object!", e);
169             return false;
170         }
171     }
172
173     private boolean isConfigurationValid() {
174         logger.debug("Start reading Thing configuration.");
175         try {
176             refreshIntervalMediumPriority = (BigDecimal) this.thing.getConfiguration()
177                     .get(MEDIUM_PRIORITY_REFRESH_TIME);
178             refreshIntervalHighPriority = (BigDecimal) this.thing.getConfiguration().get(HIGH_PRIORITY_REFRESH_TIME);
179
180             if (refreshIntervalHighPriority.intValue() <= 0 || refreshIntervalMediumPriority.intValue() <= 0) {
181                 throw new IllegalArgumentException("Refresh time must be positive number!");
182             }
183             logger.debug("Refresh time for medium priority channels set to {} s", refreshIntervalMediumPriority);
184             logger.debug("Refresh time for high priority channels set to {} s", refreshIntervalHighPriority);
185             return true;
186         } catch (IllegalArgumentException e) {
187             logger.warn("Refresh time value is invalid! Please change the thing configuration!");
188             return false;
189         } catch (ClassCastException e) {
190             logger.debug("Channel configuration cannot be read!");
191             return false;
192         }
193     }
194
195     private boolean updateProperties() {
196         Map<String, String> properties = editProperties();
197         try {
198             properties.put(PROPERTY_CPU_LOGICAL_CORES, systeminfo.getCpuLogicalCores().toString());
199             properties.put(PROPERTY_CPU_PHYSICAL_CORES, systeminfo.getCpuPhysicalCores().toString());
200             properties.put(PROPERTY_OS_FAMILY, systeminfo.getOsFamily().toString());
201             properties.put(PROPERTY_OS_MANUFACTURER, systeminfo.getOsManufacturer().toString());
202             properties.put(PROPERTY_OS_VERSION, systeminfo.getOsVersion().toString());
203             updateProperties(properties);
204             logger.debug("Properties updated!");
205             return true;
206         } catch (Exception e) {
207             logger.debug("Cannot get system properties! Please try to restart the binding.", e);
208             return false;
209         }
210     }
211
212     /**
213      * Retrieve info on available storages, drives, displays, batteries, network interfaces and fans in the system. If
214      * there is more than 1, create additional channel groups and channels representing each of the entities with an
215      * index added to the channel groups and channels. The base channel groups and channels will remain without index
216      * and are equal to the channel groups and channels with index 0. If there is only one entity in a group, do not add
217      * a channels group and channels with index 0.
218      * <p>
219      * If channel groups are added, the thing type will change to systeminfo:computer-Ext, with Ext equal to the thing
220      * id. A new handler will be created and initialization restarted. Therefore further initialization of the current
221      * handler can be aborted if the method returns true.
222      *
223      * @return true if channel groups where added
224      */
225     private boolean addDynamicChannels() {
226         ThingUID thingUID = thing.getUID();
227
228         List<ChannelGroupDefinition> newChannelGroups = new ArrayList<>();
229         newChannelGroups.addAll(createChannelGroups(thingUID, CHANNEL_GROUP_STORAGE, CHANNEL_GROUP_TYPE_STORAGE,
230                 systeminfo.getFileOSStoreCount()));
231         newChannelGroups.addAll(createChannelGroups(thingUID, CHANNEL_GROUP_DRIVE, CHANNEL_GROUP_TYPE_DRIVE,
232                 systeminfo.getDriveCount()));
233         newChannelGroups.addAll(createChannelGroups(thingUID, CHANNEL_GROUP_DISPLAY, CHANNEL_GROUP_TYPE_DISPLAY,
234                 systeminfo.getDisplayCount()));
235         newChannelGroups.addAll(createChannelGroups(thingUID, CHANNEL_GROUP_BATTERY, CHANNEL_GROUP_TYPE_BATTERY,
236                 systeminfo.getPowerSourceCount()));
237         newChannelGroups.addAll(createChannelGroups(thingUID, CHANNEL_GROUP_NETWORK, CHANNEL_GROUP_TYPE_NETWORK,
238                 systeminfo.getNetworkIFCount()));
239         if (!newChannelGroups.isEmpty()) {
240             logger.debug("Creating additional channel groups");
241             newChannelGroups.addAll(0, thingTypeProvider.getChannelGroupDefinitions(thing.getThingTypeUID()));
242             ThingTypeUID thingTypeUID = new ThingTypeUID(BINDING_ID, THING_TYPE_COMPUTER_ID + idExtString);
243             if (thingTypeProvider.updateThingType(thingTypeUID, newChannelGroups)) {
244                 logger.trace("Channel groups were added, changing the thing type");
245                 changeThingType(thingTypeUID, thing.getConfiguration());
246             } else {
247                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.HANDLER_INITIALIZING_ERROR,
248                         "@text/offline.cannot-initialize");
249             }
250             return true;
251         }
252
253         List<Channel> newChannels = new ArrayList<>();
254         newChannels.addAll(createChannels(thingUID, CHANNEL_SENSORS_FAN_SPEED, systeminfo.getFanCount()));
255         if (!newChannels.isEmpty()) {
256             logger.debug("Creating additional channels");
257             newChannels.addAll(0, thing.getChannels());
258             ThingBuilder thingBuilder = editThing();
259             thingBuilder.withChannels(newChannels);
260             updateThing(thingBuilder.build());
261         }
262
263         return false;
264     }
265
266     private List<ChannelGroupDefinition> createChannelGroups(ThingUID thingUID, String channelGroupID,
267             String channelGroupTypeID, int count) {
268         if (count <= 1) {
269             return Collections.emptyList();
270         }
271
272         List<String> channelGroups = thingTypeProvider.getChannelGroupDefinitions(thing.getThingTypeUID()).stream()
273                 .map(ChannelGroupDefinition::getId).collect(Collectors.toList());
274
275         List<ChannelGroupDefinition> newChannelGroups = new ArrayList<>();
276         for (int i = 0; i < count; i++) {
277             String index = String.valueOf(i);
278             ChannelGroupDefinition channelGroupDef = thingTypeProvider
279                     .createChannelGroupDefinitionWithIndex(channelGroupID, channelGroupTypeID, i);
280             if (!(channelGroupDef == null || channelGroups.contains(channelGroupID + index))) {
281                 logger.trace("Adding channel group {}", channelGroupID + index);
282                 newChannelGroups.add(channelGroupDef);
283             }
284         }
285         return newChannelGroups;
286     }
287
288     private List<Channel> createChannels(ThingUID thingUID, String channelID, int count) {
289         if (count <= 1) {
290             return Collections.emptyList();
291         }
292
293         List<Channel> newChannels = new ArrayList<>();
294         for (int i = 0; i < count; i++) {
295             Channel channel = thingTypeProvider.createChannelWithIndex(thing, channelID, i);
296             if (channel != null && thing.getChannel(channel.getUID()) == null) {
297                 logger.trace("Creating channel {}", channel.getUID().getId());
298                 newChannels.add(channel);
299             }
300         }
301         return newChannels;
302     }
303
304     private void storeChannelsConfig() {
305         logger.trace("Storing channel configurations");
306         thingTypeProvider.storeChannelsConfig(thing);
307     }
308
309     private void restoreChannelsConfig() {
310         logger.trace("Restoring channel configurations");
311         Map<String, Configuration> channelsConfig = thingTypeProvider.restoreChannelsConfig(thing.getUID());
312         for (String channelId : channelsConfig.keySet()) {
313             Channel channel = thing.getChannel(channelId);
314             Configuration config = channelsConfig.get(channelId);
315             if (channel != null && config != null) {
316                 Configuration currentConfig = channel.getConfiguration();
317                 for (String param : config.keySet()) {
318                     if (isConfigurationKeyChanged(currentConfig, config, param)) {
319                         handleChannelConfigurationChange(channel, config, param);
320                     }
321                 }
322             }
323         }
324     }
325
326     private void groupChannelsByPriority() {
327         logger.trace("Grouping channels by priority");
328         List<Channel> channels = this.thing.getChannels();
329
330         for (Channel channel : channels) {
331             Configuration properties = channel.getConfiguration();
332             String priority = (String) properties.get(PRIOIRITY_PARAM);
333             if (priority == null) {
334                 logger.debug("Channel with UID {} will not be updated. The channel has no priority set!",
335                         channel.getUID());
336                 break;
337             }
338             switch (priority) {
339                 case "High":
340                     highPriorityChannels.add(channel.getUID());
341                     break;
342                 case "Medium":
343                     mediumPriorityChannels.add(channel.getUID());
344                     break;
345                 case "Low":
346                     lowPriorityChannels.add(channel.getUID());
347                     break;
348                 default:
349                     logger.debug("Invalid priority configuration parameter. Channel will not be updated!");
350             }
351         }
352     }
353
354     private void changeChannelPriority(ChannelUID channelUID, String priority) {
355         switch (priority) {
356             case "High":
357                 mediumPriorityChannels.remove(channelUID);
358                 lowPriorityChannels.remove(channelUID);
359                 highPriorityChannels.add(channelUID);
360                 break;
361             case "Medium":
362                 lowPriorityChannels.remove(channelUID);
363                 highPriorityChannels.remove(channelUID);
364                 mediumPriorityChannels.add(channelUID);
365                 break;
366             case "Low":
367                 highPriorityChannels.remove(channelUID);
368                 mediumPriorityChannels.remove(channelUID);
369                 lowPriorityChannels.add(channelUID);
370                 break;
371             default:
372                 logger.debug("Invalid priority configuration parameter. Channel will not be updated!");
373         }
374     }
375
376     private void scheduleUpdates() {
377         logger.debug("Schedule high priority tasks at fixed rate {} s", refreshIntervalHighPriority);
378         highPriorityTasks = scheduler.scheduleWithFixedDelay(() -> {
379             publishData(highPriorityChannels);
380         }, WAIT_TIME_CHANNEL_ITEM_LINK_INIT, refreshIntervalHighPriority.intValue(), TimeUnit.SECONDS);
381
382         logger.debug("Schedule medium priority tasks at fixed rate {} s", refreshIntervalMediumPriority);
383         mediumPriorityTasks = scheduler.scheduleWithFixedDelay(() -> {
384             publishData(mediumPriorityChannels);
385         }, WAIT_TIME_CHANNEL_ITEM_LINK_INIT, refreshIntervalMediumPriority.intValue(), TimeUnit.SECONDS);
386
387         logger.debug("Schedule one time update for low priority tasks");
388         scheduler.schedule(() -> {
389             publishData(lowPriorityChannels);
390         }, WAIT_TIME_CHANNEL_ITEM_LINK_INIT, TimeUnit.SECONDS);
391     }
392
393     private void publishData(Set<ChannelUID> channels) {
394         // if handler disposed while waiting for the links, don't update the channel states
395         if (!ThingStatus.ONLINE.equals(thing.getStatus())) {
396             return;
397         }
398         Iterator<ChannelUID> iter = channels.iterator();
399         while (iter.hasNext()) {
400             ChannelUID channeUID = iter.next();
401             if (isLinked(channeUID.getId())) {
402                 publishDataForChannel(channeUID);
403             }
404         }
405     }
406
407     private void publishDataForChannel(ChannelUID channelUID) {
408         State state = getInfoForChannel(channelUID);
409         String channelID = channelUID.getId();
410         updateState(channelID, state);
411     }
412
413     public Set<ChannelUID> getHighPriorityChannels() {
414         return highPriorityChannels;
415     }
416
417     public Set<ChannelUID> getMediumPriorityChannels() {
418         return mediumPriorityChannels;
419     }
420
421     public Set<ChannelUID> getLowPriorityChannels() {
422         return lowPriorityChannels;
423     }
424
425     /**
426      * This method gets the information for specific channel through the {@link SysteminfoInterface}. It uses the
427      * channel ID to call the correct method from the {@link SysteminfoInterface} with deviceIndex parameter (in case of
428      * multiple devices, for reference see {@link #getDeviceIndex(String)}})
429      *
430      * @param channelUID the UID of the channel
431      * @return State object or null, if there is no information for the device with this index
432      */
433     private State getInfoForChannel(ChannelUID channelUID) {
434         State state = null;
435
436         String channelID = channelUID.getId();
437         int deviceIndex = getDeviceIndex(channelUID);
438
439         logger.trace("Getting state for channel {} with device index {}", channelID, deviceIndex);
440
441         // The channelGroup or channel may contain deviceIndex. It must be deleted from the channelID, because otherwise
442         // the switch will not find the correct method below.
443         // All digits are deleted from the ID, except for CpuLoad channels.
444         if (!(CHANNEL_CPU_LOAD_1.equals(channelID) || CHANNEL_CPU_LOAD_5.equals(channelID)
445                 || CHANNEL_CPU_LOAD_15.equals(channelID))) {
446             channelID = channelID.replaceAll("\\d+", "");
447         }
448
449         try {
450             switch (channelID) {
451                 case CHANNEL_MEMORY_HEAP_AVAILABLE:
452                     state = new QuantityType<>(Runtime.getRuntime().freeMemory(), Units.BYTE);
453                     break;
454                 case CHANNEL_MEMORY_USED_HEAP_PERCENT:
455                     state = new QuantityType<>((Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory())
456                             * 100 / Runtime.getRuntime().maxMemory(), Units.PERCENT);
457                     break;
458                 case CHANNEL_DISPLAY_INFORMATION:
459                     state = systeminfo.getDisplayInformation(deviceIndex);
460                     break;
461                 case CHANNEL_BATTERY_NAME:
462                     state = systeminfo.getBatteryName(deviceIndex);
463                     break;
464                 case CHANNEL_BATTERY_REMAINING_CAPACITY:
465                     state = new QuantityType<>(systeminfo.getBatteryRemainingCapacity(deviceIndex), Units.PERCENT);
466                     break;
467                 case CHANNEL_BATTERY_REMAINING_TIME:
468                     state = systeminfo.getBatteryRemainingTime(deviceIndex);
469                     break;
470                 case CHANNEL_SENSORS_CPU_TEMPERATURE:
471                     state = systeminfo.getSensorsCpuTemperature();
472                     break;
473                 case CHANNEL_SENOSRS_CPU_VOLTAGE:
474                     state = systeminfo.getSensorsCpuVoltage();
475                     break;
476                 case CHANNEL_SENSORS_FAN_SPEED:
477                     state = systeminfo.getSensorsFanSpeed(deviceIndex);
478                     break;
479                 case CHANNEL_CPU_LOAD:
480                     PercentType cpuLoad = cpuLoadCache.getValue();
481                     state = (cpuLoad != null) ? new QuantityType<>(cpuLoad, Units.PERCENT) : null;
482                     break;
483                 case CHANNEL_CPU_LOAD_1:
484                     state = systeminfo.getCpuLoad1();
485                     break;
486                 case CHANNEL_CPU_LOAD_5:
487                     state = systeminfo.getCpuLoad5();
488                     break;
489                 case CHANNEL_CPU_LOAD_15:
490                     state = systeminfo.getCpuLoad15();
491                     break;
492                 case CHANNEL_CPU_UPTIME:
493                     state = systeminfo.getCpuUptime();
494                     break;
495                 case CHANNEL_CPU_THREADS:
496                     state = systeminfo.getCpuThreads();
497                     break;
498                 case CHANNEL_CPU_DESCRIPTION:
499                     state = systeminfo.getCpuDescription();
500                     break;
501                 case CHANNEL_CPU_NAME:
502                     state = systeminfo.getCpuName();
503                     break;
504                 case CHANNEL_MEMORY_AVAILABLE:
505                     state = systeminfo.getMemoryAvailable();
506                     break;
507                 case CHANNEL_MEMORY_USED:
508                     state = systeminfo.getMemoryUsed();
509                     break;
510                 case CHANNEL_MEMORY_TOTAL:
511                     state = systeminfo.getMemoryTotal();
512                     break;
513                 case CHANNEL_MEMORY_AVAILABLE_PERCENT:
514                     PercentType memoryAvailablePercent = systeminfo.getMemoryAvailablePercent();
515                     state = (memoryAvailablePercent != null) ? new QuantityType<>(memoryAvailablePercent, Units.PERCENT)
516                             : null;
517                     break;
518                 case CHANNEL_MEMORY_USED_PERCENT:
519                     PercentType memoryUsedPercent = systeminfo.getMemoryUsedPercent();
520                     state = (memoryUsedPercent != null) ? new QuantityType<>(memoryUsedPercent, Units.PERCENT) : null;
521                     break;
522                 case CHANNEL_SWAP_AVAILABLE:
523                     state = systeminfo.getSwapAvailable();
524                     break;
525                 case CHANNEL_SWAP_USED:
526                     state = systeminfo.getSwapUsed();
527                     break;
528                 case CHANNEL_SWAP_TOTAL:
529                     state = systeminfo.getSwapTotal();
530                     break;
531                 case CHANNEL_SWAP_AVAILABLE_PERCENT:
532                     PercentType swapAvailablePercent = systeminfo.getSwapAvailablePercent();
533                     state = (swapAvailablePercent != null) ? new QuantityType<>(swapAvailablePercent, Units.PERCENT)
534                             : null;
535                     break;
536                 case CHANNEL_SWAP_USED_PERCENT:
537                     PercentType swapUsedPercent = systeminfo.getSwapUsedPercent();
538                     state = (swapUsedPercent != null) ? new QuantityType<>(swapUsedPercent, Units.PERCENT) : null;
539                     break;
540                 case CHANNEL_DRIVE_MODEL:
541                     state = systeminfo.getDriveModel(deviceIndex);
542                     break;
543                 case CHANNEL_DRIVE_SERIAL:
544                     state = systeminfo.getDriveSerialNumber(deviceIndex);
545                     break;
546                 case CHANNEL_DRIVE_NAME:
547                     state = systeminfo.getDriveName(deviceIndex);
548                     break;
549                 case CHANNEL_STORAGE_NAME:
550                     state = systeminfo.getStorageName(deviceIndex);
551                     break;
552                 case CHANNEL_STORAGE_DESCRIPTION:
553                     state = systeminfo.getStorageDescription(deviceIndex);
554                     break;
555                 case CHANNEL_STORAGE_AVAILABLE:
556                     state = systeminfo.getStorageAvailable(deviceIndex);
557                     break;
558                 case CHANNEL_STORAGE_USED:
559                     state = systeminfo.getStorageUsed(deviceIndex);
560                     break;
561                 case CHANNEL_STORAGE_TOTAL:
562                     state = systeminfo.getStorageTotal(deviceIndex);
563                     break;
564                 case CHANNEL_STORAGE_TYPE:
565                     state = systeminfo.getStorageType(deviceIndex);
566                     break;
567                 case CHANNEL_STORAGE_AVAILABLE_PERCENT:
568                     PercentType storageAvailablePercent = systeminfo.getStorageAvailablePercent(deviceIndex);
569                     state = (storageAvailablePercent != null)
570                             ? new QuantityType<>(storageAvailablePercent, Units.PERCENT)
571                             : null;
572                     break;
573                 case CHANNEL_STORAGE_USED_PERCENT:
574                     PercentType storageUsedPercent = systeminfo.getStorageUsedPercent(deviceIndex);
575                     state = (storageUsedPercent != null) ? new QuantityType<>(storageUsedPercent, Units.PERCENT) : null;
576                     break;
577                 case CHANNEL_NETWORK_IP:
578                     state = systeminfo.getNetworkIp(deviceIndex);
579                     break;
580                 case CHANNEL_NETWORK_ADAPTER_NAME:
581                     state = systeminfo.getNetworkDisplayName(deviceIndex);
582                     break;
583                 case CHANNEL_NETWORK_NAME:
584                     state = systeminfo.getNetworkName(deviceIndex);
585                     break;
586                 case CHANNEL_NETWORK_MAC:
587                     state = systeminfo.getNetworkMac(deviceIndex);
588                     break;
589                 case CHANNEL_NETWORK_DATA_SENT:
590                     state = systeminfo.getNetworkDataSent(deviceIndex);
591                     break;
592                 case CHANNEL_NETWORK_DATA_RECEIVED:
593                     state = systeminfo.getNetworkDataReceived(deviceIndex);
594                     break;
595                 case CHANNEL_NETWORK_PACKETS_RECEIVED:
596                     state = systeminfo.getNetworkPacketsReceived(deviceIndex);
597                     break;
598                 case CHANNEL_NETWORK_PACKETS_SENT:
599                     state = systeminfo.getNetworkPacketsSent(deviceIndex);
600                     break;
601                 case CHANNEL_PROCESS_LOAD:
602                 case CHANNEL_CURRENT_PROCESS_LOAD:
603                     DecimalType processLoad = processLoadCache.putIfAbsentAndGet(deviceIndex,
604                             () -> getProcessCpuUsage(deviceIndex));
605                     state = (processLoad != null) ? new QuantityType<>(processLoad, Units.PERCENT) : null;
606                     break;
607                 case CHANNEL_PROCESS_MEMORY:
608                 case CHANNEL_CURRENT_PROCESS_MEMORY:
609                     state = systeminfo.getProcessMemoryUsage(deviceIndex);
610                     break;
611                 case CHANNEL_PROCESS_NAME:
612                 case CHANNEL_CURRENT_PROCESS_NAME:
613                     state = systeminfo.getProcessName(deviceIndex);
614                     break;
615                 case CHANNEL_PROCESS_PATH:
616                 case CHANNEL_CURRENT_PROCESS_PATH:
617                     state = systeminfo.getProcessPath(deviceIndex);
618                     break;
619                 case CHANNEL_PROCESS_THREADS:
620                 case CHANNEL_CURRENT_PROCESS_THREADS:
621                     state = systeminfo.getProcessThreads(deviceIndex);
622                     break;
623                 default:
624                     logger.debug("Channel with unknown ID: {} !", channelID);
625             }
626         } catch (DeviceNotFoundException e) {
627             logger.warn("No information for channel {} with device index: {}", channelID, deviceIndex);
628         } catch (Exception e) {
629             logger.debug("Unexpected error occurred while getting system information!", e);
630             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "@text/offline.unexpected-error");
631         }
632         return state != null ? state : UnDefType.UNDEF;
633     }
634
635     private @Nullable PercentType getSystemCpuLoad() {
636         return systeminfo.getSystemCpuLoad();
637     }
638
639     private @Nullable DecimalType getProcessCpuUsage(int pid) {
640         try {
641             return systeminfo.getProcessCpuUsage(pid);
642         } catch (DeviceNotFoundException e) {
643             logger.warn("Process with pid {} does not exist", pid);
644             return null;
645         }
646     }
647
648     /**
649      * The device index is an optional part of the channelID - the last characters of the groupID. It is used to
650      * identify unique device, when more than one devices are available (e.g. local disks with names C:\, D:\, E"\ - the
651      * first will have deviceIndex=0, the second deviceIndex=1 ant etc).
652      * When no device index is specified, default value of 0 (first device in the list) is returned.
653      *
654      * @param channelID the ID of the channel
655      * @return natural number (number >=0)
656      */
657     private int getDeviceIndex(ChannelUID channelUID) {
658         String channelID = channelUID.getId();
659         String channelGroupID = channelUID.getGroupId();
660         if (channelGroupID == null) {
661             return 0;
662         }
663
664         if (channelGroupID.contains(CHANNEL_GROUP_PROCESS)) {
665             // Only in this case the deviceIndex is part of the channel configuration - PID (Process Identifier)
666             int pid = getPID(channelUID);
667             logger.debug("Channel with UID {} tracks process with PID: {}", channelUID, pid);
668             return pid;
669         }
670
671         if (channelGroupID.contains(CHANNEL_GROUP_CURRENT_PROCESS)) {
672             int pid = systeminfo.getCurrentProcessID();
673             return pid;
674         }
675
676         // First try to get device index in group id, delete all non-digits from id
677         if (Character.isDigit(channelGroupID.charAt(channelGroupID.length() - 1))) {
678             String deviceIndexPart = channelGroupID.replaceAll("\\D+", "");
679             return Integer.parseInt(deviceIndexPart);
680         }
681
682         // If not found, try to find it in channel id, delete all non-digits from id
683         if (Character.isDigit(channelID.charAt(channelID.length() - 1))) {
684             String deviceIndexPart = channelID.replaceAll("\\D+", "");
685             return Integer.parseInt(deviceIndexPart);
686         }
687
688         return 0;
689     }
690
691     /**
692      * This method gets the process identifier (PID) for specific process
693      *
694      * @param channelUID channel unique identifier
695      * @return natural number
696      */
697     private int getPID(ChannelUID channelUID) {
698         int pid = 0;
699         try {
700             Channel channel = this.thing.getChannel(channelUID.getId());
701             if (channel != null) {
702                 Configuration channelProperties = channel.getConfiguration();
703                 BigDecimal pidValue = (BigDecimal) channelProperties.get(PID_PARAM);
704                 if (pidValue == null || pidValue.intValue() < 0) {
705                     throw new IllegalArgumentException("Invalid value for Process Identifier.");
706                 } else {
707                     pid = pidValue.intValue();
708                 }
709             } else {
710                 logger.debug("Channel does not exist! Fall back to default value.");
711             }
712         } catch (ClassCastException e) {
713             logger.debug("Channel configuration cannot be read! Fall back to default value.", e);
714         } catch (IllegalArgumentException e) {
715             logger.debug("PID (Process Identifier) must be positive number. Fall back to default value. ", e);
716         }
717         return pid;
718     }
719
720     @Override
721     public void handleCommand(ChannelUID channelUID, Command command) {
722         if (thing.getStatus().equals(ThingStatus.ONLINE)) {
723             if (command instanceof RefreshType) {
724                 logger.debug("Refresh command received for channel {} !", channelUID);
725                 publishDataForChannel(channelUID);
726             } else {
727                 logger.debug("Unsupported command {} ! Supported commands: REFRESH", command);
728             }
729         } else {
730             logger.debug("Cannot handle command. Thing is not ONLINE.");
731         }
732     }
733
734     private boolean isConfigurationKeyChanged(Configuration currentConfig, Configuration newConfig, String key) {
735         Object currentValue = currentConfig.get(key);
736         Object newValue = newConfig.get(key);
737
738         if (currentValue == null) {
739             return (newValue != null);
740         }
741
742         return !currentValue.equals(newValue);
743     }
744
745     @Override
746     public synchronized void thingUpdated(Thing thing) {
747         logger.trace("About to update thing");
748         boolean isChannelConfigChanged = false;
749
750         List<Channel> channels = thing.getChannels();
751
752         for (Channel channel : channels) {
753             ChannelUID channelUID = channel.getUID();
754             Configuration newChannelConfig = channel.getConfiguration();
755             Channel oldChannel = this.thing.getChannel(channelUID.getId());
756
757             if (oldChannel == null) {
758                 logger.warn("Channel with UID {} cannot be updated, as it cannot be found!", channelUID);
759                 continue;
760             }
761             Configuration currentChannelConfig = oldChannel.getConfiguration();
762
763             if (isConfigurationKeyChanged(currentChannelConfig, newChannelConfig, PRIOIRITY_PARAM)) {
764                 isChannelConfigChanged = true;
765
766                 handleChannelConfigurationChange(oldChannel, newChannelConfig, PRIOIRITY_PARAM);
767
768                 String newPriority = (String) newChannelConfig.get(PRIOIRITY_PARAM);
769                 changeChannelPriority(channelUID, newPriority);
770             }
771
772             if (isConfigurationKeyChanged(currentChannelConfig, newChannelConfig, PID_PARAM)) {
773                 isChannelConfigChanged = true;
774                 handleChannelConfigurationChange(oldChannel, newChannelConfig, PID_PARAM);
775             }
776         }
777
778         if (!(isInitialized() && isChannelConfigChanged)) {
779             super.thingUpdated(thing);
780         }
781     }
782
783     private void handleChannelConfigurationChange(Channel channel, Configuration newConfig, String parameter) {
784         Configuration configuration = channel.getConfiguration();
785         Object oldValue = configuration.get(parameter);
786
787         configuration.put(parameter, newConfig.get(parameter));
788
789         Object newValue = newConfig.get(parameter);
790         logger.debug("Channel with UID {} has changed its {} from {} to {}", channel.getUID(), parameter, oldValue,
791                 newValue);
792         publishDataForChannel(channel.getUID());
793     }
794
795     @Override
796     protected void changeThingType(ThingTypeUID thingTypeUID, Configuration configuration) {
797         storeChannelsConfig();
798         super.changeThingType(thingTypeUID, configuration);
799     }
800
801     private void stopScheduledUpdates() {
802         ScheduledFuture<?> localHighPriorityTasks = highPriorityTasks;
803         if (localHighPriorityTasks != null) {
804             logger.debug("High prioriy tasks will not be run anymore!");
805             localHighPriorityTasks.cancel(true);
806         }
807
808         ScheduledFuture<?> localMediumPriorityTasks = mediumPriorityTasks;
809         if (localMediumPriorityTasks != null) {
810             logger.debug("Medium prioriy tasks will not be run anymore!");
811             localMediumPriorityTasks.cancel(true);
812         }
813     }
814
815     @Override
816     public void dispose() {
817         stopScheduledUpdates();
818     }
819 }