2 * Copyright (c) 2010-2024 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
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
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.systeminfo.internal.handler;
15 import static org.openhab.binding.systeminfo.internal.SysteminfoBindingConstants.*;
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;
25 import java.util.concurrent.ScheduledFuture;
26 import java.util.concurrent.TimeUnit;
27 import java.util.stream.Collectors;
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;
59 * The {@link SysteminfoHandler} is responsible for providing real time information about the system
60 * (CPU, Memory, Storage, Display and others).
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
69 public class SysteminfoHandler extends BaseThingHandler {
71 * Refresh interval for {@link #highPriorityChannels} in seconds.
73 private @NonNullByDefault({}) BigDecimal refreshIntervalHighPriority;
76 * Refresh interval for {@link #mediumPriorityChannels} in seconds.
78 private @NonNullByDefault({}) BigDecimal refreshIntervalMediumPriority;
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}.
85 private final Set<ChannelUID> highPriorityChannels = new HashSet<>();
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}.
92 private final Set<ChannelUID> mediumPriorityChannels = new HashSet<>();
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()}.
99 private final Set<ChannelUID> lowPriorityChannels = new HashSet<>();
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.
105 public static final int WAIT_TIME_CHANNEL_ITEM_LINK_INIT = 1;
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.
111 public final String idExtString;
113 public final SysteminfoThingTypeProvider thingTypeProvider;
115 private SysteminfoInterface systeminfo;
117 private @Nullable ScheduledFuture<?> highPriorityTasks;
118 private @Nullable ScheduledFuture<?> mediumPriorityTasks;
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
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);
132 private final Logger logger = LoggerFactory.getLogger(SysteminfoHandler.class);
134 public SysteminfoHandler(Thing thing, SysteminfoThingTypeProvider thingTypeProvider,
135 SysteminfoInterface systeminfo) {
137 this.thingTypeProvider = thingTypeProvider;
138 this.systeminfo = systeminfo;
140 idExtString = "-" + thing.getUID().getId();
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
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
153 groupChannelsByPriority();
155 updateStatus(ThingStatus.ONLINE);
158 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.HANDLER_INITIALIZING_ERROR,
159 "@text/offline.cannot-initialize");
164 public void handleRemoval() {
165 thingTypeProvider.removeThingType(thing.getThingTypeUID());
166 super.handleRemoval();
169 private boolean instantiateSysteminfoLibrary() {
171 systeminfo.initializeSysteminfo();
172 logger.debug("Systeminfo implementation is instantiated!");
174 } catch (Exception e) {
175 logger.warn("Cannot instantiate Systeminfo object!", e);
180 private boolean isConfigurationValid() {
181 logger.debug("Start reading Thing configuration.");
183 refreshIntervalMediumPriority = (BigDecimal) this.thing.getConfiguration()
184 .get(MEDIUM_PRIORITY_REFRESH_TIME);
185 refreshIntervalHighPriority = (BigDecimal) this.thing.getConfiguration().get(HIGH_PRIORITY_REFRESH_TIME);
187 if (refreshIntervalHighPriority.intValue() <= 0 || refreshIntervalMediumPriority.intValue() <= 0) {
188 throw new IllegalArgumentException("Refresh time must be positive number!");
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);
193 } catch (IllegalArgumentException e) {
194 logger.warn("Refresh time value is invalid! Please change the thing configuration!");
196 } catch (ClassCastException e) {
197 logger.debug("Channel configuration cannot be read!");
202 private boolean updateProperties() {
203 Map<String, String> properties = editProperties();
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!");
213 } catch (Exception e) {
214 logger.debug("Cannot get system properties! Please try to restart the binding.", e);
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.
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.
230 * @return true if channel groups where added
232 private boolean addDynamicChannels() {
233 ThingUID thingUID = thing.getUID();
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());
254 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.HANDLER_INITIALIZING_ERROR,
255 "@text/offline.cannot-initialize");
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());
274 private List<ChannelGroupDefinition> createChannelGroups(ThingUID thingUID, String channelGroupID,
275 String channelGroupTypeID, int count) {
277 return Collections.emptyList();
280 List<String> channelGroups = thingTypeProvider.getChannelGroupDefinitions(thing.getThingTypeUID()).stream()
281 .map(ChannelGroupDefinition::getId).collect(Collectors.toList());
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);
293 return newChannelGroups;
296 private List<Channel> createChannels(ThingUID thingUID, String channelID, int count) {
298 return Collections.emptyList();
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);
312 private void storeChannelsConfig() {
313 logger.trace("Storing channel configurations");
314 thingTypeProvider.storeChannelsConfig(thing);
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);
334 private void groupChannelsByPriority() {
335 logger.trace("Grouping channels by priority");
336 List<Channel> channels = this.thing.getChannels();
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!",
348 highPriorityChannels.add(channel.getUID());
351 mediumPriorityChannels.add(channel.getUID());
354 lowPriorityChannels.add(channel.getUID());
357 logger.debug("Invalid priority configuration parameter. Channel will not be updated!");
362 private void changeChannelPriority(ChannelUID channelUID, String priority) {
365 mediumPriorityChannels.remove(channelUID);
366 lowPriorityChannels.remove(channelUID);
367 highPriorityChannels.add(channelUID);
370 lowPriorityChannels.remove(channelUID);
371 highPriorityChannels.remove(channelUID);
372 mediumPriorityChannels.add(channelUID);
375 highPriorityChannels.remove(channelUID);
376 mediumPriorityChannels.remove(channelUID);
377 lowPriorityChannels.add(channelUID);
380 logger.debug("Invalid priority configuration parameter. Channel will not be updated!");
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);
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);
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);
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())) {
406 Iterator<ChannelUID> iter = channels.iterator();
407 while (iter.hasNext()) {
408 ChannelUID channeUID = iter.next();
409 if (isLinked(channeUID.getId())) {
410 publishDataForChannel(channeUID);
415 private void publishDataForChannel(ChannelUID channelUID) {
416 State state = getInfoForChannel(channelUID);
417 String channelID = channelUID.getId();
418 updateState(channelID, state);
421 public Set<ChannelUID> getHighPriorityChannels() {
422 return highPriorityChannels;
425 public Set<ChannelUID> getMediumPriorityChannels() {
426 return mediumPriorityChannels;
429 public Set<ChannelUID> getLowPriorityChannels() {
430 return lowPriorityChannels;
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)}})
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
441 private State getInfoForChannel(ChannelUID channelUID) {
444 String channelID = channelUID.getId();
445 int deviceIndex = getDeviceIndex(channelUID);
447 logger.trace("Getting state for channel {} with device index {}", channelID, deviceIndex);
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+", "");
459 case CHANNEL_MEMORY_HEAP_AVAILABLE:
460 state = new QuantityType<>(Runtime.getRuntime().freeMemory(), Units.BYTE);
462 case CHANNEL_MEMORY_USED_HEAP_PERCENT:
463 state = new QuantityType<>((Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory())
464 * 100 / Runtime.getRuntime().maxMemory(), Units.PERCENT);
466 case CHANNEL_DISPLAY_INFORMATION:
467 state = systeminfo.getDisplayInformation(deviceIndex);
469 case CHANNEL_BATTERY_NAME:
470 state = systeminfo.getBatteryName(deviceIndex);
472 case CHANNEL_BATTERY_REMAINING_CAPACITY:
473 state = new QuantityType<>(systeminfo.getBatteryRemainingCapacity(deviceIndex), Units.PERCENT);
475 case CHANNEL_BATTERY_REMAINING_TIME:
476 state = systeminfo.getBatteryRemainingTime(deviceIndex);
478 case CHANNEL_SENSORS_CPU_TEMPERATURE:
479 state = systeminfo.getSensorsCpuTemperature();
481 case CHANNEL_SENOSRS_CPU_VOLTAGE:
482 state = systeminfo.getSensorsCpuVoltage();
484 case CHANNEL_SENSORS_FAN_SPEED:
485 state = systeminfo.getSensorsFanSpeed(deviceIndex);
487 case CHANNEL_CPU_MAXFREQ:
488 state = systeminfo.getCpuMaxFreq();
490 case CHANNEL_CPU_FREQ:
491 state = systeminfo.getCpuFreq(deviceIndex);
493 case CHANNEL_CPU_LOAD:
494 PercentType cpuLoad = cpuLoadCache.getValue();
495 state = (cpuLoad != null) ? new QuantityType<>(cpuLoad, Units.PERCENT) : null;
497 case CHANNEL_CPU_LOAD_1:
498 state = systeminfo.getCpuLoad1();
500 case CHANNEL_CPU_LOAD_5:
501 state = systeminfo.getCpuLoad5();
503 case CHANNEL_CPU_LOAD_15:
504 state = systeminfo.getCpuLoad15();
506 case CHANNEL_CPU_UPTIME:
507 state = systeminfo.getCpuUptime();
509 case CHANNEL_CPU_THREADS:
510 state = systeminfo.getCpuThreads();
512 case CHANNEL_CPU_DESCRIPTION:
513 state = systeminfo.getCpuDescription();
515 case CHANNEL_CPU_NAME:
516 state = systeminfo.getCpuName();
518 case CHANNEL_MEMORY_AVAILABLE:
519 state = systeminfo.getMemoryAvailable();
521 case CHANNEL_MEMORY_USED:
522 state = systeminfo.getMemoryUsed();
524 case CHANNEL_MEMORY_TOTAL:
525 state = systeminfo.getMemoryTotal();
527 case CHANNEL_MEMORY_AVAILABLE_PERCENT:
528 PercentType memoryAvailablePercent = systeminfo.getMemoryAvailablePercent();
529 state = (memoryAvailablePercent != null) ? new QuantityType<>(memoryAvailablePercent, Units.PERCENT)
532 case CHANNEL_MEMORY_USED_PERCENT:
533 PercentType memoryUsedPercent = systeminfo.getMemoryUsedPercent();
534 state = (memoryUsedPercent != null) ? new QuantityType<>(memoryUsedPercent, Units.PERCENT) : null;
536 case CHANNEL_SWAP_AVAILABLE:
537 state = systeminfo.getSwapAvailable();
539 case CHANNEL_SWAP_USED:
540 state = systeminfo.getSwapUsed();
542 case CHANNEL_SWAP_TOTAL:
543 state = systeminfo.getSwapTotal();
545 case CHANNEL_SWAP_AVAILABLE_PERCENT:
546 PercentType swapAvailablePercent = systeminfo.getSwapAvailablePercent();
547 state = (swapAvailablePercent != null) ? new QuantityType<>(swapAvailablePercent, Units.PERCENT)
550 case CHANNEL_SWAP_USED_PERCENT:
551 PercentType swapUsedPercent = systeminfo.getSwapUsedPercent();
552 state = (swapUsedPercent != null) ? new QuantityType<>(swapUsedPercent, Units.PERCENT) : null;
554 case CHANNEL_DRIVE_MODEL:
555 state = systeminfo.getDriveModel(deviceIndex);
557 case CHANNEL_DRIVE_SERIAL:
558 state = systeminfo.getDriveSerialNumber(deviceIndex);
560 case CHANNEL_DRIVE_NAME:
561 state = systeminfo.getDriveName(deviceIndex);
563 case CHANNEL_STORAGE_NAME:
564 state = systeminfo.getStorageName(deviceIndex);
566 case CHANNEL_STORAGE_DESCRIPTION:
567 state = systeminfo.getStorageDescription(deviceIndex);
569 case CHANNEL_STORAGE_AVAILABLE:
570 state = systeminfo.getStorageAvailable(deviceIndex);
572 case CHANNEL_STORAGE_USED:
573 state = systeminfo.getStorageUsed(deviceIndex);
575 case CHANNEL_STORAGE_TOTAL:
576 state = systeminfo.getStorageTotal(deviceIndex);
578 case CHANNEL_STORAGE_TYPE:
579 state = systeminfo.getStorageType(deviceIndex);
581 case CHANNEL_STORAGE_AVAILABLE_PERCENT:
582 PercentType storageAvailablePercent = systeminfo.getStorageAvailablePercent(deviceIndex);
583 state = (storageAvailablePercent != null)
584 ? new QuantityType<>(storageAvailablePercent, Units.PERCENT)
587 case CHANNEL_STORAGE_USED_PERCENT:
588 PercentType storageUsedPercent = systeminfo.getStorageUsedPercent(deviceIndex);
589 state = (storageUsedPercent != null) ? new QuantityType<>(storageUsedPercent, Units.PERCENT) : null;
591 case CHANNEL_NETWORK_IP:
592 state = systeminfo.getNetworkIp(deviceIndex);
594 case CHANNEL_NETWORK_ADAPTER_NAME:
595 state = systeminfo.getNetworkDisplayName(deviceIndex);
597 case CHANNEL_NETWORK_NAME:
598 state = systeminfo.getNetworkName(deviceIndex);
600 case CHANNEL_NETWORK_MAC:
601 state = systeminfo.getNetworkMac(deviceIndex);
603 case CHANNEL_NETWORK_DATA_SENT:
604 state = systeminfo.getNetworkDataSent(deviceIndex);
606 case CHANNEL_NETWORK_DATA_RECEIVED:
607 state = systeminfo.getNetworkDataReceived(deviceIndex);
609 case CHANNEL_NETWORK_PACKETS_RECEIVED:
610 state = systeminfo.getNetworkPacketsReceived(deviceIndex);
612 case CHANNEL_NETWORK_PACKETS_SENT:
613 state = systeminfo.getNetworkPacketsSent(deviceIndex);
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;
621 case CHANNEL_PROCESS_MEMORY:
622 case CHANNEL_CURRENT_PROCESS_MEMORY:
623 state = systeminfo.getProcessMemoryUsage(deviceIndex);
625 case CHANNEL_PROCESS_NAME:
626 case CHANNEL_CURRENT_PROCESS_NAME:
627 state = systeminfo.getProcessName(deviceIndex);
629 case CHANNEL_PROCESS_PATH:
630 case CHANNEL_CURRENT_PROCESS_PATH:
631 state = systeminfo.getProcessPath(deviceIndex);
633 case CHANNEL_PROCESS_THREADS:
634 case CHANNEL_CURRENT_PROCESS_THREADS:
635 state = systeminfo.getProcessThreads(deviceIndex);
638 logger.debug("Channel with unknown ID: {} !", channelID);
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");
646 return state != null ? state : UnDefType.UNDEF;
649 private @Nullable PercentType getSystemCpuLoad() {
650 return systeminfo.getSystemCpuLoad();
653 private @Nullable DecimalType getProcessCpuUsage(int pid) {
655 return systeminfo.getProcessCpuUsage(pid);
656 } catch (DeviceNotFoundException e) {
657 logger.warn("Process with pid {} does not exist", pid);
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.
668 * @param channelID the ID of the channel
669 * @return natural number (number >=0)
671 private int getDeviceIndex(ChannelUID channelUID) {
672 String channelID = channelUID.getId();
673 String channelGroupID = channelUID.getGroupId();
674 if (channelGroupID == null) {
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);
685 if (channelGroupID.contains(CHANNEL_GROUP_CURRENT_PROCESS)) {
686 return systeminfo.getCurrentProcessID();
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);
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);
705 * This method gets the process identifier (PID) for specific process
707 * @param channelUID channel unique identifier
708 * @return natural number
710 private int getPID(ChannelUID channelUID) {
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.");
720 pid = pidValue.intValue();
723 logger.debug("Channel does not exist! Fall back to default value.");
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);
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);
740 logger.debug("Unsupported command {} ! Supported commands: REFRESH", command);
743 logger.debug("Cannot handle command. Thing is not ONLINE.");
747 private boolean isConfigurationKeyChanged(Configuration currentConfig, Configuration newConfig, String key) {
748 Object currentValue = currentConfig.get(key);
749 Object newValue = newConfig.get(key);
751 if (currentValue == null) {
752 return (newValue != null);
755 return !currentValue.equals(newValue);
759 public synchronized void thingUpdated(Thing thing) {
760 logger.trace("About to update thing");
761 boolean isChannelConfigChanged = false;
763 List<Channel> channels = thing.getChannels();
765 for (Channel channel : channels) {
766 ChannelUID channelUID = channel.getUID();
767 Configuration newChannelConfig = channel.getConfiguration();
768 Channel oldChannel = this.thing.getChannel(channelUID.getId());
770 if (oldChannel == null) {
771 logger.warn("Channel with UID {} cannot be updated, as it cannot be found!", channelUID);
774 Configuration currentChannelConfig = oldChannel.getConfiguration();
776 if (isConfigurationKeyChanged(currentChannelConfig, newChannelConfig, PRIOIRITY_PARAM)) {
777 isChannelConfigChanged = true;
779 handleChannelConfigurationChange(oldChannel, newChannelConfig, PRIOIRITY_PARAM);
781 String newPriority = (String) newChannelConfig.get(PRIOIRITY_PARAM);
782 changeChannelPriority(channelUID, newPriority);
785 if (isConfigurationKeyChanged(currentChannelConfig, newChannelConfig, PID_PARAM)) {
786 isChannelConfigChanged = true;
787 handleChannelConfigurationChange(oldChannel, newChannelConfig, PID_PARAM);
791 if (!(isInitialized() && isChannelConfigChanged)) {
792 super.thingUpdated(thing);
796 private void handleChannelConfigurationChange(Channel channel, Configuration newConfig, String parameter) {
797 Configuration configuration = channel.getConfiguration();
798 Object oldValue = configuration.get(parameter);
800 configuration.put(parameter, newConfig.get(parameter));
802 Object newValue = newConfig.get(parameter);
803 logger.debug("Channel with UID {} has changed its {} from {} to {}", channel.getUID(), parameter, oldValue,
805 publishDataForChannel(channel.getUID());
809 protected void changeThingType(ThingTypeUID thingTypeUID, Configuration configuration) {
810 storeChannelsConfig();
811 super.changeThingType(thingTypeUID, configuration);
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);
821 ScheduledFuture<?> localMediumPriorityTasks = mediumPriorityTasks;
822 if (localMediumPriorityTasks != null) {
823 logger.debug("Medium prioriy tasks will not be run anymore!");
824 localMediumPriorityTasks.cancel(true);
829 public void dispose() {
830 stopScheduledUpdates();