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