]> git.basschouten.com Git - openhab-addons.git/blob
84fd7998da373140c22abadf6ecffad9a354c144
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
7  * This program and the accompanying materials are made available under the
8  * terms of the Eclipse Public License 2.0 which is available at
9  * http://www.eclipse.org/legal/epl-2.0
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.zway.internal.handler;
14
15 import static de.fh_zwickau.informatik.sensor.ZWayConstants.*;
16 import static org.openhab.binding.zway.internal.ZWayBindingConstants.*;
17
18 import java.util.Calendar;
19 import java.util.HashMap;
20 import java.util.List;
21 import java.util.Map;
22 import java.util.concurrent.ScheduledFuture;
23 import java.util.concurrent.TimeUnit;
24
25 import org.openhab.binding.zway.internal.ZWayBindingConstants;
26 import org.openhab.binding.zway.internal.converter.ZWayDeviceStateConverter;
27 import org.openhab.core.library.types.DecimalType;
28 import org.openhab.core.library.types.HSBType;
29 import org.openhab.core.library.types.OnOffType;
30 import org.openhab.core.library.types.PercentType;
31 import org.openhab.core.library.types.StopMoveType;
32 import org.openhab.core.library.types.UpDownType;
33 import org.openhab.core.thing.Bridge;
34 import org.openhab.core.thing.Channel;
35 import org.openhab.core.thing.ChannelUID;
36 import org.openhab.core.thing.Thing;
37 import org.openhab.core.thing.ThingStatus;
38 import org.openhab.core.thing.ThingStatusDetail;
39 import org.openhab.core.thing.ThingStatusInfo;
40 import org.openhab.core.thing.binding.BaseThingHandler;
41 import org.openhab.core.thing.binding.ThingHandler;
42 import org.openhab.core.thing.binding.builder.ChannelBuilder;
43 import org.openhab.core.thing.binding.builder.ThingBuilder;
44 import org.openhab.core.thing.type.ChannelTypeUID;
45 import org.openhab.core.types.Command;
46 import org.openhab.core.types.RefreshType;
47 import org.slf4j.Logger;
48 import org.slf4j.LoggerFactory;
49
50 import de.fh_zwickau.informatik.sensor.model.devices.Device;
51 import de.fh_zwickau.informatik.sensor.model.devices.DeviceList;
52 import de.fh_zwickau.informatik.sensor.model.devices.types.Battery;
53 import de.fh_zwickau.informatik.sensor.model.devices.types.Doorlock;
54 import de.fh_zwickau.informatik.sensor.model.devices.types.SensorBinary;
55 import de.fh_zwickau.informatik.sensor.model.devices.types.SensorDiscrete;
56 import de.fh_zwickau.informatik.sensor.model.devices.types.SensorMultilevel;
57 import de.fh_zwickau.informatik.sensor.model.devices.types.SwitchBinary;
58 import de.fh_zwickau.informatik.sensor.model.devices.types.SwitchControl;
59 import de.fh_zwickau.informatik.sensor.model.devices.types.SwitchMultilevel;
60 import de.fh_zwickau.informatik.sensor.model.devices.types.SwitchRGBW;
61 import de.fh_zwickau.informatik.sensor.model.devices.types.SwitchToggle;
62 import de.fh_zwickau.informatik.sensor.model.devices.types.Thermostat;
63 import de.fh_zwickau.informatik.sensor.model.devices.types.ToggleButton;
64 import de.fh_zwickau.informatik.sensor.model.zwaveapi.devices.ZWaveDevice;
65
66 /**
67  * The {@link ZWayDeviceHandler} is responsible for handling commands, which are
68  * sent to one of the channels.
69  *
70  * @author Patrick Hecker - Initial contribution, remove observer mechanism
71  * @author Johannes Einig - Now uses the bridge handler cached device list
72  */
73 public abstract class ZWayDeviceHandler extends BaseThingHandler {
74     private final Logger logger = LoggerFactory.getLogger(getClass());
75
76     private DevicePolling devicePolling;
77     private ScheduledFuture<?> pollingJob;
78     protected Calendar lastUpdate;
79
80     protected abstract void refreshLastUpdate();
81
82     /**
83      * Initialize polling job
84      */
85     private class Initializer implements Runnable {
86
87         @Override
88         public void run() {
89             // https://community.openhab.org/t/oh2-major-bug-with-scheduled-jobs/12350/11
90             // If any execution of the task encounters an exception, subsequent executions are
91             // suppressed. Otherwise, the task will only terminate via cancellation or
92             // termination of the executor.
93             try {
94                 // Z-Way bridge have to be ONLINE because configuration is needed
95                 ZWayBridgeHandler zwayBridgeHandler = getZWayBridgeHandler();
96                 if (zwayBridgeHandler == null || !zwayBridgeHandler.getThing().getStatus().equals(ThingStatus.ONLINE)) {
97                     logger.debug("Z-Way bridge handler not found or not ONLINE.");
98                     return;
99                 }
100
101                 // Initialize device polling
102                 if (pollingJob == null || pollingJob.isCancelled()) {
103                     logger.debug("Starting polling job at intervall {}",
104                             zwayBridgeHandler.getZWayBridgeConfiguration().getPollingInterval());
105                     pollingJob = scheduler.scheduleWithFixedDelay(devicePolling, 10,
106                             zwayBridgeHandler.getZWayBridgeConfiguration().getPollingInterval(), TimeUnit.SECONDS);
107                 } else {
108                     // Called when thing or bridge updated ...
109                     logger.debug("Polling is allready active");
110                 }
111             } catch (Throwable t) {
112                 if (t instanceof Exception) {
113                     logger.error("{}", t.getMessage());
114                 } else if (t instanceof Error) {
115                     logger.error("{}", t.getMessage());
116                 } else {
117                     logger.error("Unexpected error");
118                 }
119                 if (getThing().getStatus() == ThingStatus.ONLINE) {
120                     updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.HANDLER_INITIALIZING_ERROR,
121                             "Error occurred when starting polling.");
122                 }
123             }
124         }
125     }
126
127     private class Disposer implements Runnable {
128
129         @Override
130         public void run() {
131             // Z-Way bridge have to be ONLINE because configuration is needed
132             ZWayBridgeHandler zwayBridgeHandler = getZWayBridgeHandler();
133             if (zwayBridgeHandler == null || !zwayBridgeHandler.getThing().getStatus().equals(ThingStatus.ONLINE)) {
134                 logger.debug("Z-Way bridge handler not found or not ONLINE.");
135
136                 // status update will remove finally
137                 updateStatus(ThingStatus.REMOVED);
138
139                 return;
140             }
141
142             // status update will remove finally
143             updateStatus(ThingStatus.REMOVED);
144         }
145     }
146
147     public ZWayDeviceHandler(Thing thing) {
148         super(thing);
149
150         devicePolling = new DevicePolling();
151     }
152
153     protected synchronized ZWayBridgeHandler getZWayBridgeHandler() {
154         Bridge bridge = getBridge();
155         if (bridge == null) {
156             return null;
157         }
158         ThingHandler handler = bridge.getHandler();
159         if (handler instanceof ZWayBridgeHandler bridgeHandler) {
160             return bridgeHandler;
161         } else {
162             return null;
163         }
164     }
165
166     @Override
167     public void initialize() {
168         setLocation();
169
170         // Start an extra thread to check the connection, because it takes sometimes more
171         // than 5000 milliseconds and the handler will suspend (ThingStatus.UNINITIALIZED).
172         scheduler.execute(new Initializer());
173     }
174
175     @Override
176     public void dispose() {
177         if (pollingJob != null && !pollingJob.isCancelled()) {
178             pollingJob.cancel(true);
179             pollingJob = null;
180         }
181
182         super.dispose();
183     }
184
185     @Override
186     public void handleRemoval() {
187         logger.debug("Handle removal Z-Way device ...");
188
189         // Start an extra thread, because it takes sometimes more
190         // than 5000 milliseconds and the handler will suspend (ThingStatus.UNINITIALIZED).
191         scheduler.execute(new Disposer());
192
193         // super.handleRemoval() called in every case in scheduled task ...
194     }
195
196     @Override
197     public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
198         // Only called if status ONLINE or OFFLINE
199         logger.debug("Z-Way bridge status changed: {}", bridgeStatusInfo);
200
201         if (bridgeStatusInfo.getStatus().equals(ThingStatus.OFFLINE)) {
202             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE, "Bridge status is offline.");
203         } else if (bridgeStatusInfo.getStatus().equals(ThingStatus.ONLINE)) {
204             // Initialize thing, if all OK the status of device thing will be ONLINE
205
206             // Start an extra thread to check the connection, because it takes sometimes more
207             // than 5000 milliseconds and the handler will suspend (ThingStatus.UNINITIALIZED).
208             scheduler.execute(new Initializer());
209         }
210     }
211
212     private class DevicePolling implements Runnable {
213         @Override
214         public void run() {
215             logger.debug("Starting polling for device: {}", getThing().getLabel());
216             if (getThing().getStatus().equals(ThingStatus.ONLINE)) {
217                 // Refresh device states
218                 for (Channel channel : getThing().getChannels()) {
219                     logger.debug("Checking link state of channel: {}", channel.getLabel());
220                     if (isLinked(channel.getUID().getId())) {
221                         logger.debug("Refresh items that linked with channel: {}", channel.getLabel());
222
223                         // https://community.openhab.org/t/oh2-major-bug-with-scheduled-jobs/12350/11
224                         // If any execution of the task encounters an exception, subsequent executions are
225                         // suppressed. Otherwise, the task will only terminate via cancellation or
226                         // termination of the executor.
227                         try {
228                             refreshChannel(channel);
229                         } catch (Throwable t) {
230                             if (t instanceof Exception) {
231                                 logger.error("Error occurred when performing polling:{}", t.getMessage());
232                             } else if (t instanceof Error) {
233                                 logger.error("Error occurred when performing polling:{}", t.getMessage());
234                             } else {
235                                 logger.error("Error occurred when performing polling: Unexpected error");
236                             }
237                             if (getThing().getStatus() == ThingStatus.ONLINE) {
238                                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE,
239                                         "Error occurred when performing polling.");
240                             }
241                         }
242                     } else {
243                         logger.debug("Polling for device: {} not possible (channel {} not linked", thing.getLabel(),
244                                 channel.getLabel());
245                     }
246                 }
247
248                 // Refresh last update
249                 refreshLastUpdate();
250             } else {
251                 logger.debug("Polling not possible, Z-Way device isn't ONLINE");
252             }
253         }
254     }
255
256     private synchronized void setLocation() {
257         Map<String, String> properties = getThing().getProperties();
258         // Load location from properties
259         String location = properties.get(ZWayBindingConstants.DEVICE_PROP_LOCATION);
260         if (location != null && !location.isBlank() && getThing().getLocation() == null) {
261             logger.debug("Set location to {}", location);
262             ThingBuilder thingBuilder = editThing();
263             thingBuilder.withLocation(location);
264             thingBuilder.withLabel(thing.getLabel());
265             updateThing(thingBuilder.build());
266         }
267     }
268
269     protected void refreshAllChannels() {
270         scheduler.execute(new DevicePolling());
271     }
272
273     private void refreshChannel(Channel channel) {
274         // Check Z-Way bridge handler
275         ZWayBridgeHandler zwayBridgeHandler = getZWayBridgeHandler();
276         if (zwayBridgeHandler == null || !zwayBridgeHandler.getThing().getStatus().equals(ThingStatus.ONLINE)) {
277             logger.debug("Z-Way bridge handler not found or not ONLINE.");
278             return;
279         }
280
281         // Check device id associated with channel
282         String deviceId = channel.getProperties().get("deviceId");
283         if (deviceId != null) {
284             // Load and check device from Z-Way server
285             DeviceList deviceList = zwayBridgeHandler.getZWayApi().getDevices();
286             if (deviceList != null) {
287                 // 1.) Load only the current value from Z-Way server
288                 Device device = deviceList.getDeviceById(deviceId);
289                 if (device == null) {
290                     logger.debug("ZAutomation device not found.");
291                     return;
292                 }
293
294                 try {
295                     updateState(channel.getUID(), ZWayDeviceStateConverter.toState(device, channel));
296                 } catch (IllegalArgumentException iae) {
297                     logger.debug(
298                             "IllegalArgumentException ({}) during refresh channel for device: {} (level: {}) with channel: {}",
299                             iae.getMessage(), device.getMetrics().getTitle(), device.getMetrics().getLevel(),
300                             channel.getChannelTypeUID());
301
302                     updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE,
303                             "Channel refresh for device: " + device.getMetrics().getTitle() + " (level: "
304                                     + device.getMetrics().getLevel() + ") with channel: " + channel.getChannelTypeUID()
305                                     + " failed!");
306                 }
307                 // 2.) Trigger update function, soon as the value has been updated, openHAB will be notified
308                 try {
309                     device.update();
310                 } catch (Exception e) {
311                     logger.debug("{} doesn't support update (triggered during refresh channel)",
312                             device.getMetrics().getTitle());
313                 }
314             } else {
315                 logger.warn("Devices not loaded");
316             }
317         } else {
318             // Check channel for command classes
319             // Channel thermostat mode
320             if (channel.getUID().equals(new ChannelUID(getThing().getUID(), THERMOSTAT_MODE_CC_CHANNEL))) {
321                 // Load physical device
322                 String nodeIdString = channel.getProperties().get("nodeId");
323                 ZWaveDevice physicalDevice = nodeIdString == null ? null
324                         : zwayBridgeHandler.getZWayApi().getZWaveDevice(Integer.parseInt(nodeIdString));
325                 if (physicalDevice != null) {
326                     updateState(channel.getUID(), new DecimalType(physicalDevice.getInstances().get0()
327                             .getCommandClasses().get64().getData().getMode().getValue()));
328                 }
329             }
330         }
331     }
332
333     @Override
334     public void channelLinked(ChannelUID channelUID) {
335         logger.debug("Z-Way device channel linked: {}", channelUID);
336
337         ZWayBridgeHandler zwayBridgeHandler = getZWayBridgeHandler();
338         if (zwayBridgeHandler == null || !zwayBridgeHandler.getThing().getStatus().equals(ThingStatus.ONLINE)) {
339             logger.debug("Z-Way bridge handler not found or not ONLINE.");
340             return;
341         }
342
343         // Method called when channel linked and not when server started!!!
344
345         super.channelLinked(channelUID); // performs a refresh command
346     }
347
348     @Override
349     public void channelUnlinked(ChannelUID channelUID) {
350         logger.debug("Z-Way device channel unlinked: {}", channelUID);
351
352         ZWayBridgeHandler zwayBridgeHandler = getZWayBridgeHandler();
353         if (zwayBridgeHandler == null || !zwayBridgeHandler.getThing().getStatus().equals(ThingStatus.ONLINE)) {
354             logger.debug("Z-Way bridge handler not found or not ONLINE.");
355             return;
356         }
357
358         super.channelUnlinked(channelUID);
359     }
360
361     @Override
362     public void handleCommand(ChannelUID channelUID, final Command command) {
363         logger.debug("Handle command for channel: {} with command: {}", channelUID.getId(), command.toString());
364
365         // Check Z-Way bridge handler
366         ZWayBridgeHandler zwayBridgeHandler = getZWayBridgeHandler();
367         if (zwayBridgeHandler == null || !zwayBridgeHandler.getThing().getStatus().equals(ThingStatus.ONLINE)) {
368             logger.debug("Z-Way bridge handler not found or not ONLINE.");
369             return;
370         }
371
372         // Load device id from channel's properties for the compatibility of ZAutomation and ZWave devices
373         final Channel channel = getThing().getChannel(channelUID.getId());
374         final String deviceId = channel != null ? channel.getProperties().get("deviceId") : null;
375
376         if (deviceId != null) {
377             DeviceList deviceList = zwayBridgeHandler.getDeviceList();
378             if (deviceList != null) {
379                 Device device = deviceList.getDeviceById(deviceId);
380                 if (device == null) {
381                     logger.debug("ZAutomation device not found.");
382                     return;
383                 }
384
385                 try {
386                     if (command instanceof RefreshType) {
387                         logger.debug("Handle command: RefreshType");
388
389                         refreshChannel(channel);
390                     } else {
391                         if (device instanceof Battery) {
392                             // possible commands: update()
393                         } else if (device instanceof Doorlock) {
394                             // possible commands: open(), close()
395                             if (command instanceof OnOffType) {
396                                 logger.debug("Handle command: OnOffType");
397                                 if (command.equals(OnOffType.ON)) {
398                                     device.open();
399                                 } else if (command.equals(OnOffType.OFF)) {
400                                     device.close();
401                                 }
402                             }
403                         } else if (device instanceof SensorBinary) {
404                             // possible commands: update()
405                         } else if (device instanceof SensorMultilevel) {
406                             // possible commands: update()
407                         } else if (device instanceof SwitchBinary) {
408                             // possible commands: update(), on(), off()
409                             if (command instanceof OnOffType) {
410                                 logger.debug("Handle command: OnOffType");
411
412                                 if (command.equals(OnOffType.ON)) {
413                                     device.on();
414                                 } else if (command.equals(OnOffType.OFF)) {
415                                     device.off();
416                                 }
417                             }
418                         } else if (device instanceof SwitchMultilevel) {
419                             // possible commands: update(), on(), up(), off(), down(), min(), max(), upMax(),
420                             // increase(), decrease(), exact(level), exactSmooth(level, duration), stop(), startUp(),
421                             // startDown()
422                             if (command instanceof DecimalType || command instanceof PercentType) {
423                                 logger.debug("Handle command: DecimalType");
424
425                                 device.exact(command.toString());
426                             } else if (command instanceof UpDownType) {
427                                 if (command.equals(UpDownType.UP)) {
428                                     logger.debug("Handle command: UpDownType.Up");
429
430                                     device.startUp();
431                                 } else if (command.equals(UpDownType.DOWN)) {
432                                     logger.debug("Handle command: UpDownType.Down");
433
434                                     device.startDown();
435                                 }
436                             } else if (command instanceof StopMoveType) {
437                                 logger.debug("Handle command: StopMoveType");
438
439                                 device.stop();
440                             } else if (command instanceof OnOffType) {
441                                 logger.debug("Handle command: OnOffType");
442
443                                 if (command.equals(OnOffType.ON)) {
444                                     device.on();
445                                 } else if (command.equals(OnOffType.OFF)) {
446                                     device.off();
447                                 }
448                             }
449                         } else if (device instanceof SwitchRGBW) {
450                             // possible commands: on(), off(), exact(red, green, blue)
451                             if (command instanceof HSBType hsb) {
452                                 logger.debug("Handle command: HSBType");
453
454                                 // first set on/off
455                                 if (hsb.getBrightness().intValue() > 0) {
456                                     if ("off".equals(device.getMetrics().getLevel().toLowerCase())) {
457                                         device.on();
458                                     }
459
460                                     // then set color
461                                     int red = (int) Math.round(255 * (hsb.getRed().doubleValue() / 100));
462                                     int green = (int) Math.round(255 * (hsb.getGreen().doubleValue() / 100));
463                                     int blue = (int) Math.round(255 * (hsb.getBlue().doubleValue() / 100));
464
465                                     device.exact(red, green, blue);
466                                 } else {
467                                     device.off();
468                                 }
469                             }
470                         } else if (device instanceof Thermostat) {
471                             if (command instanceof DecimalType) {
472                                 logger.debug("Handle command: DecimalType");
473
474                                 device.exact(command.toString());
475                             }
476                         } else if (device instanceof SwitchControl) {
477                             // possible commands: on(), off(), exact(level), upstart(), upstop(), downstart(),
478                             // downstop()
479                             if (command instanceof OnOffType) {
480                                 logger.debug("Handle command: OnOffType");
481
482                                 if (command.equals(OnOffType.ON)) {
483                                     device.on();
484                                 } else if (command.equals(OnOffType.OFF)) {
485                                     device.off();
486                                 }
487                             }
488                         } else if (device instanceof ToggleButton || device instanceof SwitchToggle) {
489                             // possible commands: on(), off(), exact(level), upstart(), upstop(), downstart(),
490                             // downstop()
491                             if (command instanceof OnOffType) {
492                                 logger.debug("Handle command: OnOffType");
493
494                                 if (command.equals(OnOffType.ON)) {
495                                     device.on();
496                                 } // no else - only ON command is sent to Z-Way
497                             }
498                         }
499                     }
500                 } catch (UnsupportedOperationException e) {
501                     logger.warn("Unknown command: {}", e.getMessage());
502                 }
503             } else {
504                 logger.warn("Devices not loaded");
505             }
506         } else if (channel.getUID().equals(new ChannelUID(getThing().getUID(), THERMOSTAT_MODE_CC_CHANNEL))) {
507             // Load physical device
508             if (command instanceof DecimalType) {
509                 String nodeIdString = channel.getProperties().get("nodeId");
510                 logger.debug("Handle command: DecimalType");
511                 if (nodeIdString != null) {
512                     zwayBridgeHandler.getZWayApi().getZWaveDeviceThermostatModeSet(Integer.parseInt(nodeIdString),
513                             Integer.parseInt(command.toString()));
514                 }
515             } else if (command instanceof RefreshType) {
516                 logger.debug("Handle command: RefreshType");
517                 refreshChannel(channel);
518             }
519         }
520     }
521
522     protected synchronized void addDeviceAsChannel(Device device) {
523         // Device.probeType
524         // |
525         // Device.metrics.probeType
526         // |
527         // Device.metrics.icon
528         // |
529         // Command class
530         // |
531         // Default, depends on device type
532
533         if (device != null) {
534             logger.debug("Add virtual device as channel: {}", device.getMetrics().getTitle());
535
536             HashMap<String, String> properties = new HashMap<>();
537             properties.put("deviceId", device.getDeviceId());
538
539             String id = "";
540             String acceptedItemType = "";
541
542             // 1. Set basically channel types without further information
543             if (device instanceof Battery) {
544                 id = BATTERY_CHANNEL;
545                 acceptedItemType = "Number";
546             } else if (device instanceof Doorlock) {
547                 id = DOORLOCK_CHANNEL;
548                 acceptedItemType = "Switch";
549             } else if (device instanceof SensorBinary) {
550                 id = SENSOR_BINARY_CHANNEL;
551                 acceptedItemType = "Switch";
552             } else if (device instanceof SensorMultilevel) {
553                 id = SENSOR_MULTILEVEL_CHANNEL;
554                 acceptedItemType = "Number";
555             } else if (device instanceof SwitchBinary) {
556                 id = SWITCH_BINARY_CHANNEL;
557                 acceptedItemType = "Switch";
558             } else if (device instanceof SwitchMultilevel) {
559                 id = SWITCH_MULTILEVEL_CHANNEL;
560                 acceptedItemType = "Dimmer";
561             } else if (device instanceof SwitchRGBW) {
562                 id = SWITCH_COLOR_CHANNEL;
563                 acceptedItemType = "Color";
564             } else if (device instanceof Thermostat) {
565                 id = THERMOSTAT_SET_POINT_CHANNEL;
566                 acceptedItemType = "Number";
567             } else if (device instanceof SwitchControl) {
568                 id = SWITCH_CONTROL_CHANNEL;
569                 acceptedItemType = "Switch";
570             } else if (device instanceof ToggleButton || device instanceof SwitchToggle) {
571                 id = SWITCH_CONTROL_CHANNEL;
572                 acceptedItemType = "Switch";
573             } else if (device instanceof SensorDiscrete) {
574                 id = SENSOR_DISCRETE_CHANNEL;
575                 acceptedItemType = "Number";
576             }
577
578             // 2. Check if device information includes further information about sensor type
579             if (!"".equals(device.getProbeType())) {
580                 if (device instanceof SensorMultilevel) {
581                     switch (device.getProbeType()) {
582                         case PROBE_TYPE_TEMPERATURE:
583                             id = SENSOR_TEMPERATURE_CHANNEL;
584                             acceptedItemType = "Number";
585                             break;
586                         case PROBE_TYPE_LUMINOSITY:
587                             id = SENSOR_LUMINOSITY_CHANNEL;
588                             acceptedItemType = "Number";
589                             break;
590                         case PROBE_TYPE_HUMIDITY:
591                             id = SENSOR_HUMIDITY_CHANNEL;
592                             acceptedItemType = "Number";
593                             break;
594                         case PROBE_TYPE_BAROMETER:
595                             id = SENSOR_BAROMETER_CHANNEL;
596                             acceptedItemType = "Number";
597                             break;
598                         case PROBE_TYPE_ULTRAVIOLET:
599                             id = SENSOR_ULTRAVIOLET_CHANNEL;
600                             acceptedItemType = "Number";
601                             break;
602                         case PROBE_TYPE_ENERGY:
603                             id = SENSOR_ENERGY_CHANNEL;
604                             acceptedItemType = "Number";
605                             break;
606                         case PROBE_TYPE_METER_ELECTRIC_KILOWATT_PER_HOUR:
607                             id = SENSOR_METER_KWH_CHANNEL;
608                             acceptedItemType = "Number";
609                             break;
610                         case PROBE_TYPE_METER_ELECTRIC_WATT:
611                             id = SENSOR_METER_W_CHANNEL;
612                             acceptedItemType = "Number";
613                             break;
614                         default:
615                             break;
616                     }
617                 } else if (device instanceof SensorBinary) {
618                     switch (device.getProbeType()) {
619                         case PROBE_TYPE_GENERAL_PURPOSE:
620                             if (device.getMetrics().getIcon().equals(ICON_MOTION)) {
621                                 id = SENSOR_MOTION_CHANNEL;
622                                 acceptedItemType = "Switch";
623                             }
624                             break;
625                         case PROBE_TYPE_SMOKE:
626                             id = SENSOR_SMOKE_CHANNEL;
627                             acceptedItemType = "Switch";
628                             break;
629                         case PROBE_TYPE_CO:
630                             id = SENSOR_CO_CHANNEL;
631                             acceptedItemType = "Switch";
632                             break;
633                         case PROBE_TYPE_FLOOD:
634                             id = SENSOR_FLOOD_CHANNEL;
635                             acceptedItemType = "Switch";
636                             break;
637                         case PROBE_TYPE_COOLING:
638                             // TODO
639                             break;
640                         case PROBE_TYPE_TAMPER:
641                             id = SENSOR_TAMPER_CHANNEL;
642                             acceptedItemType = "Switch";
643                             break;
644                         case PROBE_TYPE_DOOR_WINDOW:
645                             id = SENSOR_DOOR_WINDOW_CHANNEL;
646                             acceptedItemType = "Contact";
647                             break;
648                         case PROBE_TYPE_MOTION:
649                             id = SENSOR_MOTION_CHANNEL;
650                             acceptedItemType = "Switch";
651                             break;
652                         default:
653                             break;
654                     }
655                 } else if (device instanceof SwitchMultilevel) {
656                     switch (device.getProbeType()) {
657                         case PROBE_TYPE_SWITCH_COLOR_COLD_WHITE:
658                             id = SWITCH_COLOR_TEMPERATURE_CHANNEL;
659                             acceptedItemType = "Dimmer";
660                             break;
661                         case PROBE_TYPE_SWITCH_COLOR_SOFT_WHITE:
662                             id = SWITCH_COLOR_TEMPERATURE_CHANNEL;
663                             acceptedItemType = "Dimmer";
664                             break;
665                         case PROBE_TYPE_MOTOR:
666                             id = SWITCH_ROLLERSHUTTER_CHANNEL;
667                             acceptedItemType = "Rollershutter";
668                         default:
669                             break;
670                     }
671                 } else if (device instanceof SwitchBinary) {
672                     switch (device.getProbeType()) {
673                         case PROBE_TYPE_THERMOSTAT_MODE:
674                             id = THERMOSTAT_MODE_CHANNEL;
675                             acceptedItemType = "Switch";
676                             break;
677                         default:
678                             break;
679                     }
680                 }
681             } else if (!"".equals(device.getMetrics().getProbeTitle())) {
682                 if (device instanceof SensorMultilevel) {
683                     switch (device.getMetrics().getProbeTitle()) {
684                         case PROBE_TITLE_CO2_LEVEL:
685                             id = SENSOR_CO2_CHANNEL;
686                             acceptedItemType = "Number";
687                             break;
688                         default:
689                             break;
690                     }
691                 }
692             } else if (!"".equals(device.getMetrics().getIcon())) {
693                 if (device instanceof SwitchBinary) {
694                     switch (device.getMetrics().getIcon()) {
695                         case ICON_SWITCH:
696                             id = SWITCH_POWER_OUTLET_CHANNEL;
697                             acceptedItemType = "Switch";
698                             break;
699                         default:
700                             break;
701                     }
702                 }
703             } else {
704                 // Eventually take account of the command classes
705             }
706
707             // If at least one rule could mapped to a channel
708             if (!id.isBlank()) {
709                 addChannel(id, acceptedItemType, device.getMetrics().getTitle(), properties);
710
711                 logger.debug(
712                         "Channel for virtual device added with channel id: {}, accepted item type: {} and title: {}",
713                         id, acceptedItemType, device.getMetrics().getTitle());
714             } else {
715                 // Thing status will not be updated because thing could have more than one channel
716                 logger.warn("No channel for virtual device added: {}", device);
717             }
718         }
719     }
720
721     private synchronized void addChannel(String id, String acceptedItemType, String label,
722             HashMap<String, String> properties) {
723         boolean channelExists = false;
724
725         // Check if a channel for this virtual device exist. Attention: same channel type could multiple assigned to a
726         // thing. That's why not check the existence of channel type.
727         List<Channel> channels = getThing().getChannels();
728         for (Channel channel : channels) {
729             if (channel.getProperties().get("deviceId") != null
730                     && channel.getProperties().get("deviceId").equals(properties.get("deviceId"))) {
731                 channelExists = true;
732             }
733         }
734
735         if (!channelExists) {
736             ThingBuilder thingBuilder = editThing();
737             ChannelTypeUID channelTypeUID = new ChannelTypeUID(BINDING_ID, id);
738             Channel channel = ChannelBuilder
739                     .create(new ChannelUID(getThing().getUID(), id + "-" + properties.get("deviceId")),
740                             acceptedItemType)
741                     .withType(channelTypeUID).withLabel(label).withProperties(properties).build();
742             thingBuilder.withChannel(channel);
743             thingBuilder.withLabel(thing.getLabel());
744             updateThing(thingBuilder.build());
745         }
746     }
747
748     protected synchronized void addCommandClassThermostatModeAsChannel(Map<Integer, String> modes, Integer nodeId) {
749         logger.debug("Add command class thermostat mode as channel");
750
751         ChannelUID channelUID = new ChannelUID(getThing().getUID(), THERMOSTAT_MODE_CC_CHANNEL);
752
753         boolean channelExists = false;
754
755         // Check if a channel for this virtual device exist. Attention: same channel type could multiple assigned to a
756         // thing. That's why not check the existence of channel type.
757         List<Channel> channels = getThing().getChannels();
758         for (Channel channel : channels) {
759             if (channel.getUID().equals(channelUID)) {
760                 channelExists = true;
761             }
762         }
763
764         if (!channelExists) {
765             // Prepare properties (convert modes map)
766             HashMap<String, String> properties = new HashMap<>();
767
768             // Add node id (for refresh and command handling)
769             properties.put("nodeId", nodeId.toString());
770
771             // Add channel
772             ThingBuilder thingBuilder = editThing();
773
774             Channel channel = ChannelBuilder.create(channelUID, "Number")
775                     .withType(new ChannelTypeUID(BINDING_ID, THERMOSTAT_MODE_CC_CHANNEL))
776                     .withLabel("Thermostat mode (Command Class)").withDescription("Possible modes: " + modes.toString())
777                     .withProperties(properties).build();
778             thingBuilder.withChannel(channel);
779             thingBuilder.withLabel(thing.getLabel());
780             updateThing(thingBuilder.build());
781         }
782     }
783 }