2 * Copyright (c) 2010-2023 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.zway.internal.handler;
15 import static de.fh_zwickau.informatik.sensor.ZWayConstants.*;
16 import static org.openhab.binding.zway.internal.ZWayBindingConstants.*;
18 import java.util.Calendar;
19 import java.util.HashMap;
20 import java.util.List;
22 import java.util.concurrent.ScheduledFuture;
23 import java.util.concurrent.TimeUnit;
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;
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;
67 * The {@link ZWayDeviceHandler} is responsible for handling commands, which are
68 * sent to one of the channels.
70 * @author Patrick Hecker - Initial contribution, remove observer mechanism
71 * @author Johannes Einig - Now uses the bridge handler cached device list
73 public abstract class ZWayDeviceHandler extends BaseThingHandler {
74 private final Logger logger = LoggerFactory.getLogger(getClass());
76 private DevicePolling devicePolling;
77 private ScheduledFuture<?> pollingJob;
78 protected Calendar lastUpdate;
80 protected abstract void refreshLastUpdate();
83 * Initialize polling job
85 private class Initializer implements Runnable {
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.
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.");
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);
108 // Called when thing or bridge updated ...
109 logger.debug("Polling is allready active");
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());
117 logger.error("Unexpected error");
119 if (getThing().getStatus() == ThingStatus.ONLINE) {
120 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.HANDLER_INITIALIZING_ERROR,
121 "Error occurred when starting polling.");
127 private class Disposer implements Runnable {
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.");
136 // status update will remove finally
137 updateStatus(ThingStatus.REMOVED);
142 // status update will remove finally
143 updateStatus(ThingStatus.REMOVED);
147 public ZWayDeviceHandler(Thing thing) {
150 devicePolling = new DevicePolling();
153 protected synchronized ZWayBridgeHandler getZWayBridgeHandler() {
154 Bridge bridge = getBridge();
155 if (bridge == null) {
158 ThingHandler handler = bridge.getHandler();
159 if (handler instanceof ZWayBridgeHandler bridgeHandler) {
160 return bridgeHandler;
167 public void initialize() {
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());
176 public void dispose() {
177 if (pollingJob != null && !pollingJob.isCancelled()) {
178 pollingJob.cancel(true);
186 public void handleRemoval() {
187 logger.debug("Handle removal Z-Way device ...");
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());
193 // super.handleRemoval() called in every case in scheduled task ...
197 public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
198 // Only called if status ONLINE or OFFLINE
199 logger.debug("Z-Way bridge status changed: {}", bridgeStatusInfo);
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
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());
212 private class DevicePolling implements Runnable {
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());
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.
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());
235 logger.error("Error occurred when performing polling: Unexpected error");
237 if (getThing().getStatus() == ThingStatus.ONLINE) {
238 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE,
239 "Error occurred when performing polling.");
243 logger.debug("Polling for device: {} not possible (channel {} not linked", thing.getLabel(),
248 // Refresh last update
251 logger.debug("Polling not possible, Z-Way device isn't ONLINE");
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());
269 protected void refreshAllChannels() {
270 scheduler.execute(new DevicePolling());
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.");
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.");
295 updateState(channel.getUID(), ZWayDeviceStateConverter.toState(device, channel));
296 } catch (IllegalArgumentException iae) {
298 "IllegalArgumentException ({}) during refresh channel for device: {} (level: {}) with channel: {}",
299 iae.getMessage(), device.getMetrics().getTitle(), device.getMetrics().getLevel(),
300 channel.getChannelTypeUID());
302 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE,
303 "Channel refresh for device: " + device.getMetrics().getTitle() + " (level: "
304 + device.getMetrics().getLevel() + ") with channel: " + channel.getChannelTypeUID()
307 // 2.) Trigger update function, soon as the value has been updated, openHAB will be notified
310 } catch (Exception e) {
311 logger.debug("{} doesn't support update (triggered during refresh channel)",
312 device.getMetrics().getTitle());
315 logger.warn("Devices not loaded");
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()));
334 public void channelLinked(ChannelUID channelUID) {
335 logger.debug("Z-Way device channel linked: {}", channelUID);
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.");
343 // Method called when channel linked and not when server started!!!
345 super.channelLinked(channelUID); // performs a refresh command
349 public void channelUnlinked(ChannelUID channelUID) {
350 logger.debug("Z-Way device channel unlinked: {}", channelUID);
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.");
358 super.channelUnlinked(channelUID);
362 public void handleCommand(ChannelUID channelUID, final Command command) {
363 logger.debug("Handle command for channel: {} with command: {}", channelUID.getId(), command.toString());
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.");
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;
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.");
386 if (command instanceof RefreshType) {
387 logger.debug("Handle command: RefreshType");
389 refreshChannel(channel);
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)) {
399 } else if (command.equals(OnOffType.OFF)) {
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");
412 if (command.equals(OnOffType.ON)) {
414 } else if (command.equals(OnOffType.OFF)) {
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(),
422 if (command instanceof DecimalType || command instanceof PercentType) {
423 logger.debug("Handle command: DecimalType");
425 device.exact(command.toString());
426 } else if (command instanceof UpDownType) {
427 if (command.equals(UpDownType.UP)) {
428 logger.debug("Handle command: UpDownType.Up");
431 } else if (command.equals(UpDownType.DOWN)) {
432 logger.debug("Handle command: UpDownType.Down");
436 } else if (command instanceof StopMoveType) {
437 logger.debug("Handle command: StopMoveType");
440 } else if (command instanceof OnOffType) {
441 logger.debug("Handle command: OnOffType");
443 if (command.equals(OnOffType.ON)) {
445 } else if (command.equals(OnOffType.OFF)) {
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");
455 if (hsb.getBrightness().intValue() > 0) {
456 if ("off".equals(device.getMetrics().getLevel().toLowerCase())) {
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));
465 device.exact(red, green, blue);
470 } else if (device instanceof Thermostat) {
471 if (command instanceof DecimalType) {
472 logger.debug("Handle command: DecimalType");
474 device.exact(command.toString());
476 } else if (device instanceof SwitchControl) {
477 // possible commands: on(), off(), exact(level), upstart(), upstop(), downstart(),
479 if (command instanceof OnOffType) {
480 logger.debug("Handle command: OnOffType");
482 if (command.equals(OnOffType.ON)) {
484 } else if (command.equals(OnOffType.OFF)) {
488 } else if (device instanceof ToggleButton || device instanceof SwitchToggle) {
489 // possible commands: on(), off(), exact(level), upstart(), upstop(), downstart(),
491 if (command instanceof OnOffType) {
492 logger.debug("Handle command: OnOffType");
494 if (command.equals(OnOffType.ON)) {
496 } // no else - only ON command is sent to Z-Way
500 } catch (UnsupportedOperationException e) {
501 logger.warn("Unknown command: {}", e.getMessage());
504 logger.warn("Devices not loaded");
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()));
515 } else if (command instanceof RefreshType) {
516 logger.debug("Handle command: RefreshType");
517 refreshChannel(channel);
522 protected synchronized void addDeviceAsChannel(Device device) {
525 // Device.metrics.probeType
527 // Device.metrics.icon
531 // Default, depends on device type
533 if (device != null) {
534 logger.debug("Add virtual device as channel: {}", device.getMetrics().getTitle());
536 HashMap<String, String> properties = new HashMap<>();
537 properties.put("deviceId", device.getDeviceId());
540 String acceptedItemType = "";
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";
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";
586 case PROBE_TYPE_LUMINOSITY:
587 id = SENSOR_LUMINOSITY_CHANNEL;
588 acceptedItemType = "Number";
590 case PROBE_TYPE_HUMIDITY:
591 id = SENSOR_HUMIDITY_CHANNEL;
592 acceptedItemType = "Number";
594 case PROBE_TYPE_BAROMETER:
595 id = SENSOR_BAROMETER_CHANNEL;
596 acceptedItemType = "Number";
598 case PROBE_TYPE_ULTRAVIOLET:
599 id = SENSOR_ULTRAVIOLET_CHANNEL;
600 acceptedItemType = "Number";
602 case PROBE_TYPE_ENERGY:
603 id = SENSOR_ENERGY_CHANNEL;
604 acceptedItemType = "Number";
606 case PROBE_TYPE_METER_ELECTRIC_KILOWATT_PER_HOUR:
607 id = SENSOR_METER_KWH_CHANNEL;
608 acceptedItemType = "Number";
610 case PROBE_TYPE_METER_ELECTRIC_WATT:
611 id = SENSOR_METER_W_CHANNEL;
612 acceptedItemType = "Number";
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";
625 case PROBE_TYPE_SMOKE:
626 id = SENSOR_SMOKE_CHANNEL;
627 acceptedItemType = "Switch";
630 id = SENSOR_CO_CHANNEL;
631 acceptedItemType = "Switch";
633 case PROBE_TYPE_FLOOD:
634 id = SENSOR_FLOOD_CHANNEL;
635 acceptedItemType = "Switch";
637 case PROBE_TYPE_COOLING:
640 case PROBE_TYPE_TAMPER:
641 id = SENSOR_TAMPER_CHANNEL;
642 acceptedItemType = "Switch";
644 case PROBE_TYPE_DOOR_WINDOW:
645 id = SENSOR_DOOR_WINDOW_CHANNEL;
646 acceptedItemType = "Contact";
648 case PROBE_TYPE_MOTION:
649 id = SENSOR_MOTION_CHANNEL;
650 acceptedItemType = "Switch";
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";
661 case PROBE_TYPE_SWITCH_COLOR_SOFT_WHITE:
662 id = SWITCH_COLOR_TEMPERATURE_CHANNEL;
663 acceptedItemType = "Dimmer";
665 case PROBE_TYPE_MOTOR:
666 id = SWITCH_ROLLERSHUTTER_CHANNEL;
667 acceptedItemType = "Rollershutter";
671 } else if (device instanceof SwitchBinary) {
672 switch (device.getProbeType()) {
673 case PROBE_TYPE_THERMOSTAT_MODE:
674 id = THERMOSTAT_MODE_CHANNEL;
675 acceptedItemType = "Switch";
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";
692 } else if (!"".equals(device.getMetrics().getIcon())) {
693 if (device instanceof SwitchBinary) {
694 switch (device.getMetrics().getIcon()) {
696 id = SWITCH_POWER_OUTLET_CHANNEL;
697 acceptedItemType = "Switch";
704 // Eventually take account of the command classes
707 // If at least one rule could mapped to a channel
709 addChannel(id, acceptedItemType, device.getMetrics().getTitle(), properties);
712 "Channel for virtual device added with channel id: {}, accepted item type: {} and title: {}",
713 id, acceptedItemType, device.getMetrics().getTitle());
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);
721 private synchronized void addChannel(String id, String acceptedItemType, String label,
722 HashMap<String, String> properties) {
723 boolean channelExists = false;
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;
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")),
741 .withType(channelTypeUID).withLabel(label).withProperties(properties).build();
742 thingBuilder.withChannel(channel);
743 thingBuilder.withLabel(thing.getLabel());
744 updateThing(thingBuilder.build());
748 protected synchronized void addCommandClassThermostatModeAsChannel(Map<Integer, String> modes, Integer nodeId) {
749 logger.debug("Add command class thermostat mode as channel");
751 ChannelUID channelUID = new ChannelUID(getThing().getUID(), THERMOSTAT_MODE_CC_CHANNEL);
753 boolean channelExists = false;
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;
764 if (!channelExists) {
765 // Prepare properties (convert modes map)
766 HashMap<String, String> properties = new HashMap<>();
768 // Add node id (for refresh and command handling)
769 properties.put("nodeId", nodeId.toString());
772 ThingBuilder thingBuilder = editThing();
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());