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