]> git.basschouten.com Git - openhab-addons.git/blob
f67059eea25e421266ee26b4bfecef5d03770417
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2022 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) {
160             return (ZWayBridgeHandler) handler;
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.equals("") && 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.getProperties().get("deviceId");
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) {
452                                 logger.debug("Handle command: HSBType");
453
454                                 HSBType hsb = (HSBType) command;
455
456                                 // first set on/off
457                                 if (hsb.getBrightness().intValue() > 0) {
458                                     if (device.getMetrics().getLevel().toLowerCase().equals("off")) {
459                                         device.on();
460                                     }
461
462                                     // then set color
463                                     int red = (int) Math.round(255 * (hsb.getRed().doubleValue() / 100));
464                                     int green = (int) Math.round(255 * (hsb.getGreen().doubleValue() / 100));
465                                     int blue = (int) Math.round(255 * (hsb.getBlue().doubleValue() / 100));
466
467                                     device.exact(red, green, blue);
468                                 } else {
469                                     device.off();
470                                 }
471                             }
472                         } else if (device instanceof Thermostat) {
473                             if (command instanceof DecimalType) {
474                                 logger.debug("Handle command: DecimalType");
475
476                                 device.exact(command.toString());
477                             }
478                         } else if (device instanceof SwitchControl) {
479                             // possible commands: on(), off(), exact(level), upstart(), upstop(), downstart(),
480                             // downstop()
481                             if (command instanceof OnOffType) {
482                                 logger.debug("Handle command: OnOffType");
483
484                                 if (command.equals(OnOffType.ON)) {
485                                     device.on();
486                                 } else if (command.equals(OnOffType.OFF)) {
487                                     device.off();
488                                 }
489                             }
490                         } else if (device instanceof ToggleButton || device instanceof SwitchToggle) {
491                             // possible commands: on(), off(), exact(level), upstart(), upstop(), downstart(),
492                             // downstop()
493                             if (command instanceof OnOffType) {
494                                 logger.debug("Handle command: OnOffType");
495
496                                 if (command.equals(OnOffType.ON)) {
497                                     device.on();
498                                 } // no else - only ON command is sent to Z-Way
499                             }
500                         }
501                     }
502                 } catch (UnsupportedOperationException e) {
503                     logger.warn("Unknown command: {}", e.getMessage());
504                 }
505             } else {
506                 logger.warn("Devices not loaded");
507             }
508         } else if (channel.getUID().equals(new ChannelUID(getThing().getUID(), THERMOSTAT_MODE_CC_CHANNEL))) {
509             // Load physical device
510             if (command instanceof DecimalType) {
511                 String nodeIdString = channel.getProperties().get("nodeId");
512                 logger.debug("Handle command: DecimalType");
513                 if (nodeIdString != null) {
514                     zwayBridgeHandler.getZWayApi().getZWaveDeviceThermostatModeSet(Integer.parseInt(nodeIdString),
515                             Integer.parseInt(command.toString()));
516                 }
517             } else if (command instanceof RefreshType) {
518                 logger.debug("Handle command: RefreshType");
519                 refreshChannel(channel);
520             }
521         }
522     }
523
524     protected synchronized void addDeviceAsChannel(Device device) {
525         // Device.probeType
526         // |
527         // Device.metrics.probeType
528         // |
529         // Device.metrics.icon
530         // |
531         // Command class
532         // |
533         // Default, depends on device type
534
535         if (device != null) {
536             logger.debug("Add virtual device as channel: {}", device.getMetrics().getTitle());
537
538             HashMap<String, String> properties = new HashMap<>();
539             properties.put("deviceId", device.getDeviceId());
540
541             String id = "";
542             String acceptedItemType = "";
543
544             // 1. Set basically channel types without further information
545             if (device instanceof Battery) {
546                 id = BATTERY_CHANNEL;
547                 acceptedItemType = "Number";
548             } else if (device instanceof Doorlock) {
549                 id = DOORLOCK_CHANNEL;
550                 acceptedItemType = "Switch";
551             } else if (device instanceof SensorBinary) {
552                 id = SENSOR_BINARY_CHANNEL;
553                 acceptedItemType = "Switch";
554             } else if (device instanceof SensorMultilevel) {
555                 id = SENSOR_MULTILEVEL_CHANNEL;
556                 acceptedItemType = "Number";
557             } else if (device instanceof SwitchBinary) {
558                 id = SWITCH_BINARY_CHANNEL;
559                 acceptedItemType = "Switch";
560             } else if (device instanceof SwitchMultilevel) {
561                 id = SWITCH_MULTILEVEL_CHANNEL;
562                 acceptedItemType = "Dimmer";
563             } else if (device instanceof SwitchRGBW) {
564                 id = SWITCH_COLOR_CHANNEL;
565                 acceptedItemType = "Color";
566             } else if (device instanceof Thermostat) {
567                 id = THERMOSTAT_SET_POINT_CHANNEL;
568                 acceptedItemType = "Number";
569             } else if (device instanceof SwitchControl) {
570                 id = SWITCH_CONTROL_CHANNEL;
571                 acceptedItemType = "Switch";
572             } else if (device instanceof ToggleButton || device instanceof SwitchToggle) {
573                 id = SWITCH_CONTROL_CHANNEL;
574                 acceptedItemType = "Switch";
575             } else if (device instanceof SensorDiscrete) {
576                 id = SENSOR_DISCRETE_CHANNEL;
577                 acceptedItemType = "Number";
578             }
579
580             // 2. Check if device information includes further information about sensor type
581             if (!device.getProbeType().equals("")) {
582                 if (device instanceof SensorMultilevel) {
583                     switch (device.getProbeType()) {
584                         case PROBE_TYPE_TEMPERATURE:
585                             id = SENSOR_TEMPERATURE_CHANNEL;
586                             acceptedItemType = "Number";
587                             break;
588                         case PROBE_TYPE_LUMINOSITY:
589                             id = SENSOR_LUMINOSITY_CHANNEL;
590                             acceptedItemType = "Number";
591                             break;
592                         case PROBE_TYPE_HUMIDITY:
593                             id = SENSOR_HUMIDITY_CHANNEL;
594                             acceptedItemType = "Number";
595                             break;
596                         case PROBE_TYPE_BAROMETER:
597                             id = SENSOR_BAROMETER_CHANNEL;
598                             acceptedItemType = "Number";
599                             break;
600                         case PROBE_TYPE_ULTRAVIOLET:
601                             id = SENSOR_ULTRAVIOLET_CHANNEL;
602                             acceptedItemType = "Number";
603                             break;
604                         case PROBE_TYPE_ENERGY:
605                             id = SENSOR_ENERGY_CHANNEL;
606                             acceptedItemType = "Number";
607                             break;
608                         case PROBE_TYPE_METER_ELECTRIC_KILOWATT_PER_HOUR:
609                             id = SENSOR_METER_KWH_CHANNEL;
610                             acceptedItemType = "Number";
611                             break;
612                         case PROBE_TYPE_METER_ELECTRIC_WATT:
613                             id = SENSOR_METER_W_CHANNEL;
614                             acceptedItemType = "Number";
615                             break;
616                         default:
617                             break;
618                     }
619                 } else if (device instanceof SensorBinary) {
620                     switch (device.getProbeType()) {
621                         case PROBE_TYPE_GENERAL_PURPOSE:
622                             if (device.getMetrics().getIcon().equals(ICON_MOTION)) {
623                                 id = SENSOR_MOTION_CHANNEL;
624                                 acceptedItemType = "Switch";
625                             }
626                             break;
627                         case PROBE_TYPE_SMOKE:
628                             id = SENSOR_SMOKE_CHANNEL;
629                             acceptedItemType = "Switch";
630                             break;
631                         case PROBE_TYPE_CO:
632                             id = SENSOR_CO_CHANNEL;
633                             acceptedItemType = "Switch";
634                             break;
635                         case PROBE_TYPE_FLOOD:
636                             id = SENSOR_FLOOD_CHANNEL;
637                             acceptedItemType = "Switch";
638                             break;
639                         case PROBE_TYPE_COOLING:
640                             // TODO
641                             break;
642                         case PROBE_TYPE_TAMPER:
643                             id = SENSOR_TAMPER_CHANNEL;
644                             acceptedItemType = "Switch";
645                             break;
646                         case PROBE_TYPE_DOOR_WINDOW:
647                             id = SENSOR_DOOR_WINDOW_CHANNEL;
648                             acceptedItemType = "Contact";
649                             break;
650                         case PROBE_TYPE_MOTION:
651                             id = SENSOR_MOTION_CHANNEL;
652                             acceptedItemType = "Switch";
653                             break;
654                         default:
655                             break;
656                     }
657                 } else if (device instanceof SwitchMultilevel) {
658                     switch (device.getProbeType()) {
659                         case PROBE_TYPE_SWITCH_COLOR_COLD_WHITE:
660                             id = SWITCH_COLOR_TEMPERATURE_CHANNEL;
661                             acceptedItemType = "Dimmer";
662                             break;
663                         case PROBE_TYPE_SWITCH_COLOR_SOFT_WHITE:
664                             id = SWITCH_COLOR_TEMPERATURE_CHANNEL;
665                             acceptedItemType = "Dimmer";
666                             break;
667                         case PROBE_TYPE_MOTOR:
668                             id = SWITCH_ROLLERSHUTTER_CHANNEL;
669                             acceptedItemType = "Rollershutter";
670                         default:
671                             break;
672                     }
673                 } else if (device instanceof SwitchBinary) {
674                     switch (device.getProbeType()) {
675                         case PROBE_TYPE_THERMOSTAT_MODE:
676                             id = THERMOSTAT_MODE_CHANNEL;
677                             acceptedItemType = "Switch";
678                             break;
679                         default:
680                             break;
681                     }
682                 }
683             } else if (!device.getMetrics().getProbeTitle().equals("")) {
684                 if (device instanceof SensorMultilevel) {
685                     switch (device.getMetrics().getProbeTitle()) {
686                         case PROBE_TITLE_CO2_LEVEL:
687                             id = SENSOR_CO2_CHANNEL;
688                             acceptedItemType = "Number";
689                             break;
690                         default:
691                             break;
692                     }
693                 }
694             } else if (!device.getMetrics().getIcon().equals("")) {
695                 if (device instanceof SwitchBinary) {
696                     switch (device.getMetrics().getIcon()) {
697                         case ICON_SWITCH:
698                             id = SWITCH_POWER_OUTLET_CHANNEL;
699                             acceptedItemType = "Switch";
700                             break;
701                         default:
702                             break;
703                     }
704                 }
705             } else {
706                 // Eventually take account of the command classes
707             }
708
709             // If at least one rule could mapped to a channel
710             if (!id.equals("")) {
711                 addChannel(id, acceptedItemType, device.getMetrics().getTitle(), properties);
712
713                 logger.debug(
714                         "Channel for virtual device added with channel id: {}, accepted item type: {} and title: {}",
715                         id, acceptedItemType, device.getMetrics().getTitle());
716             } else {
717                 // Thing status will not be updated because thing could have more than one channel
718                 logger.warn("No channel for virtual device added: {}", device);
719             }
720         }
721     }
722
723     private synchronized void addChannel(String id, String acceptedItemType, String label,
724             HashMap<String, String> properties) {
725         boolean channelExists = false;
726
727         // Check if a channel for this virtual device exist. Attention: same channel type could multiple assigned to a
728         // thing. That's why not check the existence of channel type.
729         List<Channel> channels = getThing().getChannels();
730         for (Channel channel : channels) {
731             if (channel.getProperties().get("deviceId") != null
732                     && channel.getProperties().get("deviceId").equals(properties.get("deviceId"))) {
733                 channelExists = true;
734             }
735         }
736
737         if (!channelExists) {
738             ThingBuilder thingBuilder = editThing();
739             ChannelTypeUID channelTypeUID = new ChannelTypeUID(BINDING_ID, id);
740             Channel channel = ChannelBuilder
741                     .create(new ChannelUID(getThing().getUID(), id + "-" + properties.get("deviceId")),
742                             acceptedItemType)
743                     .withType(channelTypeUID).withLabel(label).withProperties(properties).build();
744             thingBuilder.withChannel(channel);
745             thingBuilder.withLabel(thing.getLabel());
746             updateThing(thingBuilder.build());
747         }
748     }
749
750     protected synchronized void addCommandClassThermostatModeAsChannel(Map<Integer, String> modes, Integer nodeId) {
751         logger.debug("Add command class thermostat mode as channel");
752
753         ChannelUID channelUID = new ChannelUID(getThing().getUID(), THERMOSTAT_MODE_CC_CHANNEL);
754
755         boolean channelExists = false;
756
757         // Check if a channel for this virtual device exist. Attention: same channel type could multiple assigned to a
758         // thing. That's why not check the existence of channel type.
759         List<Channel> channels = getThing().getChannels();
760         for (Channel channel : channels) {
761             if (channel.getUID().equals(channelUID)) {
762                 channelExists = true;
763             }
764         }
765
766         if (!channelExists) {
767             // Prepare properties (convert modes map)
768             HashMap<String, String> properties = new HashMap<>();
769
770             // Add node id (for refresh and command handling)
771             properties.put("nodeId", nodeId.toString());
772
773             // Add channel
774             ThingBuilder thingBuilder = editThing();
775
776             Channel channel = ChannelBuilder.create(channelUID, "Number")
777                     .withType(new ChannelTypeUID(BINDING_ID, THERMOSTAT_MODE_CC_CHANNEL))
778                     .withLabel("Thermostat mode (Command Class)").withDescription("Possible modes: " + modes.toString())
779                     .withProperties(properties).build();
780             thingBuilder.withChannel(channel);
781             thingBuilder.withLabel(thing.getLabel());
782             updateThing(thingBuilder.build());
783         }
784     }
785 }