]> git.basschouten.com Git - openhab-addons.git/blob
c39a3415f988fca779d7a63b6b49050310814aa7
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2020 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.velux.internal.handler;
14
15 import java.util.Map;
16 import java.util.concurrent.ConcurrentHashMap;
17 import java.util.concurrent.ScheduledExecutorService;
18 import java.util.concurrent.ScheduledFuture;
19 import java.util.concurrent.TimeUnit;
20
21 import org.eclipse.jdt.annotation.NonNullByDefault;
22 import org.eclipse.jdt.annotation.Nullable;
23 import org.openhab.binding.velux.internal.VeluxBinding;
24 import org.openhab.binding.velux.internal.VeluxBindingConstants;
25 import org.openhab.binding.velux.internal.VeluxItemType;
26 import org.openhab.binding.velux.internal.bridge.VeluxBridge;
27 import org.openhab.binding.velux.internal.bridge.VeluxBridgeActuators;
28 import org.openhab.binding.velux.internal.bridge.VeluxBridgeDeviceStatus;
29 import org.openhab.binding.velux.internal.bridge.VeluxBridgeGetFirmware;
30 import org.openhab.binding.velux.internal.bridge.VeluxBridgeGetHouseStatus;
31 import org.openhab.binding.velux.internal.bridge.VeluxBridgeInstance;
32 import org.openhab.binding.velux.internal.bridge.VeluxBridgeLANConfig;
33 import org.openhab.binding.velux.internal.bridge.VeluxBridgeProvider;
34 import org.openhab.binding.velux.internal.bridge.VeluxBridgeScenes;
35 import org.openhab.binding.velux.internal.bridge.VeluxBridgeSetHouseStatusMonitor;
36 import org.openhab.binding.velux.internal.bridge.VeluxBridgeWLANConfig;
37 import org.openhab.binding.velux.internal.bridge.common.BridgeAPI;
38 import org.openhab.binding.velux.internal.bridge.common.BridgeCommunicationProtocol;
39 import org.openhab.binding.velux.internal.bridge.json.JsonVeluxBridge;
40 import org.openhab.binding.velux.internal.bridge.slip.SlipVeluxBridge;
41 import org.openhab.binding.velux.internal.config.VeluxBridgeConfiguration;
42 import org.openhab.binding.velux.internal.development.Threads;
43 import org.openhab.binding.velux.internal.handler.utils.ExtendedBaseBridgeHandler;
44 import org.openhab.binding.velux.internal.handler.utils.Thing2VeluxActuator;
45 import org.openhab.binding.velux.internal.handler.utils.ThingProperty;
46 import org.openhab.binding.velux.internal.things.VeluxExistingProducts;
47 import org.openhab.binding.velux.internal.things.VeluxExistingScenes;
48 import org.openhab.binding.velux.internal.things.VeluxProduct;
49 import org.openhab.binding.velux.internal.things.VeluxProduct.ProductBridgeIndex;
50 import org.openhab.binding.velux.internal.things.VeluxProductPosition;
51 import org.openhab.binding.velux.internal.utils.Localization;
52 import org.openhab.core.common.AbstractUID;
53 import org.openhab.core.common.ThreadPoolManager;
54 import org.openhab.core.library.types.DecimalType;
55 import org.openhab.core.library.types.OnOffType;
56 import org.openhab.core.library.types.PercentType;
57 import org.openhab.core.thing.Bridge;
58 import org.openhab.core.thing.ChannelUID;
59 import org.openhab.core.thing.ThingStatus;
60 import org.openhab.core.thing.ThingStatusDetail;
61 import org.openhab.core.thing.ThingTypeUID;
62 import org.openhab.core.types.Command;
63 import org.openhab.core.types.RefreshType;
64 import org.openhab.core.types.State;
65 import org.slf4j.Logger;
66 import org.slf4j.LoggerFactory;
67
68 /**
69  * <B>Common interaction with the </B><I>Velux</I><B> bridge.</B>
70  * <P>
71  * It implements the communication between <B>OpenHAB</B> and the <I>Velux</I> Bridge:
72  * <UL>
73  * <LI><B>OpenHAB</B> Event Bus &rarr; <I>Velux</I> <B>bridge</B>
74  * <P>
75  * Sending commands and value updates.</LI>
76  * </UL>
77  * <UL>
78  * <LI><I>Velux</I> <B>bridge</B> &rarr; <B>OpenHAB</B>:
79  * <P>
80  * Retrieving information by sending a Refresh command.</LI>
81  * </UL>
82  * <P>
83  * Entry point for this class is the method
84  * {@link VeluxBridgeHandler#handleCommand handleCommand}.
85  *
86  * @author Guenther Schreiner - Initial contribution.
87  */
88 @NonNullByDefault
89 public class VeluxBridgeHandler extends ExtendedBaseBridgeHandler implements VeluxBridgeInstance, VeluxBridgeProvider {
90     private final Logger logger = LoggerFactory.getLogger(VeluxBridgeHandler.class);
91
92     // Class internal
93
94     /**
95      * Scheduler for continuous refresh by scheduleWithFixedDelay.
96      */
97     private @Nullable ScheduledFuture<?> refreshJob = null;
98
99     /**
100      * Counter of refresh invocations by {@link refreshJob}.
101      */
102     private int refreshCounter = 0;
103
104     /**
105      * Dedicated thread pool for the long-running bridge communication threads.
106      */
107     private ScheduledExecutorService handleScheduler = ThreadPoolManager
108             .getScheduledPool(VeluxBindingConstants.BINDING_ID);
109
110     private VeluxBridge myJsonBridge = new JsonVeluxBridge(this);
111     private VeluxBridge mySlipBridge = new SlipVeluxBridge(this);
112
113     /*
114      * **************************************
115      * ***** Default visibility Objects *****
116      */
117
118     VeluxBridge thisBridge = myJsonBridge;
119     public BridgeParameters bridgeParameters = new BridgeParameters();
120     Localization localization;
121
122     /**
123      * Mapping from ChannelUID to class Thing2VeluxActuator, which return Velux device information, probably cached.
124      */
125     Map<ChannelUID, Thing2VeluxActuator> channel2VeluxActuator = new ConcurrentHashMap<>();
126
127     /**
128      * Information retrieved by {@link VeluxBinding#VeluxBinding}.
129      */
130     private VeluxBridgeConfiguration veluxBridgeConfiguration = new VeluxBridgeConfiguration();
131
132     /*
133      * ************************
134      * ***** Constructors *****
135      */
136
137     public VeluxBridgeHandler(final Bridge bridge, Localization localization) {
138         super(bridge);
139         logger.trace("VeluxBridgeHandler(constructor with bridge={}, localization={}) called.", bridge, localization);
140         this.localization = localization;
141         logger.debug("Creating a VeluxBridgeHandler for thing '{}'.", getThing().getUID());
142     }
143
144     // Private classes
145
146     /**
147      * <P>
148      * Set of information retrieved from the bridge/gateway:
149      * </P>
150      * <UL>
151      * <LI>{@link #actuators} - Already known actuators,</LI>
152      * <LI>{@link #scenes} - Already on the gateway defined scenes,</LI>
153      * <LI>{@link #gateway} - Current status of the gateway status,</LI>
154      * <LI>{@link #firmware} - Information about the gateway firmware revision,</LI>
155      * <LI>{@link #lanConfig} - Information about the gateway configuration,</LI>
156      * <LI>{@link #wlanConfig} - Information about the gateway configuration.</LI>
157      * </UL>
158      */
159     @NonNullByDefault
160     public class BridgeParameters {
161         /** Information retrieved by {@link VeluxBridgeActuators#getProducts} */
162         public VeluxBridgeActuators actuators = new VeluxBridgeActuators();
163
164         /** Information retrieved by {@link org.openhab.binding.velux.internal.bridge.VeluxBridgeScenes#getScenes} */
165         VeluxBridgeScenes scenes = new VeluxBridgeScenes();
166
167         /** Information retrieved by {@link VeluxBridgeDeviceStatus#retrieve} */
168         VeluxBridgeDeviceStatus.Channel gateway = new VeluxBridgeDeviceStatus().getChannel();
169
170         /** Information retrieved by {@link VeluxBridgeGetFirmware#retrieve} */
171         VeluxBridgeGetFirmware.Channel firmware = new VeluxBridgeGetFirmware().getChannel();
172
173         /** Information retrieved by {@link VeluxBridgeLANConfig#retrieve} */
174         VeluxBridgeLANConfig.Channel lanConfig = new VeluxBridgeLANConfig().getChannel();
175
176         /** Information retrieved by {@link VeluxBridgeWLANConfig#retrieve} */
177         VeluxBridgeWLANConfig.Channel wlanConfig = new VeluxBridgeWLANConfig().getChannel();
178     }
179
180     // Private methods
181
182     /**
183      * Provide the ThingType for a given Channel.
184      * <P>
185      * Separated into this private method to deal with the deprecated method.
186      * </P>
187      *
188      * @param channelUID for type {@link ChannelUID}.
189      * @return thingTypeUID of type {@link ThingTypeUID}.
190      */
191     ThingTypeUID thingTypeUIDOf(ChannelUID channelUID) {
192         String[] segments = channelUID.getAsString().split(AbstractUID.SEPARATOR);
193         if (segments.length > 1) {
194             return new ThingTypeUID(segments[0], segments[1]);
195         }
196         logger.warn("thingTypeUIDOf({}) failed.", channelUID);
197         return new ThingTypeUID(VeluxBindingConstants.BINDING_ID, VeluxBindingConstants.UNKNOWN_THING_TYPE_ID);
198     }
199
200     // Objects and Methods for interface VeluxBridgeInstance
201
202     /**
203      * Information retrieved by ...
204      */
205     @Override
206     public VeluxBridgeConfiguration veluxBridgeConfiguration() {
207         return veluxBridgeConfiguration;
208     };
209
210     /**
211      * Information retrieved by {@link VeluxBridgeActuators#getProducts}
212      */
213     @Override
214     public VeluxExistingProducts existingProducts() {
215         return bridgeParameters.actuators.getChannel().existingProducts;
216     };
217
218     /**
219      * Information retrieved by {@link VeluxBridgeScenes#getScenes}
220      */
221     @Override
222     public VeluxExistingScenes existingScenes() {
223         return bridgeParameters.scenes.getChannel().existingScenes;
224     }
225
226     // Objects and Methods for interface VeluxBridgeProvider *****
227
228     @Override
229     public boolean bridgeCommunicate(BridgeCommunicationProtocol communication) {
230         logger.warn("bridgeCommunicate() called. Should never be called (as implemented by protocol-specific layers).");
231         return false;
232     }
233
234     @Override
235     public @Nullable BridgeAPI bridgeAPI() {
236         logger.warn("bridgeAPI() called. Should never be called (as implemented by protocol-specific layers).");
237         return null;
238     }
239
240     // Provisioning/Deprovisioning methods *****
241
242     @Override
243     public void initialize() {
244         logger.info("Initializing Velux Bridge '{}'.", getThing().getUID());
245         // The framework requires you to return from this method quickly.
246         // Setting the thing status to UNKNOWN temporarily and let the background task decide for the real status.
247         logger.trace("initialize() called.");
248         updateStatus(ThingStatus.UNKNOWN);
249         // Take care of unusual situations...
250         if (scheduler.isShutdown()) {
251             logger.warn("initialize(): scheduler is shutdown, aborting the initialization of this bridge.");
252             return;
253         }
254         if (handleScheduler.isShutdown()) {
255             logger.trace("initialize(): handleScheduler is shutdown, aborting the initialization of this bridge.");
256             return;
257         }
258         logger.trace("initialize(): preparing background initialization task.");
259         // Background initialization...
260         scheduler.execute(() -> {
261             logger.trace("initialize.scheduled(): Further work within scheduler.execute().");
262             logger.trace("initialize.scheduled(): Initializing bridge configuration parameters.");
263             this.veluxBridgeConfiguration = new VeluxBinding(getConfigAs(VeluxBridgeConfiguration.class)).checked();
264             logger.trace("initialize.scheduled(): work on updated bridge configuration parameters.");
265             bridgeParamsUpdated();
266
267             logger.debug("initialize.scheduled(): activated scheduler with {} milliseconds.",
268                     this.veluxBridgeConfiguration.refreshMSecs);
269             refreshJob = scheduler.scheduleWithFixedDelay(() -> {
270                 try {
271                     refreshOpenHAB();
272                 } catch (RuntimeException e) {
273                     logger.warn("Exception occurred during activated refresh scheduler: {}.", e.getMessage());
274                 }
275             }, this.veluxBridgeConfiguration.refreshMSecs, this.veluxBridgeConfiguration.refreshMSecs,
276                     TimeUnit.MILLISECONDS);
277             logger.trace("initialize.scheduled(): done.");
278         });
279         logger.trace("initialize() done.");
280     }
281
282     /**
283      * NOTE: It takes care about shutting down the connections before removal of this binding.
284      */
285     @Override
286     public synchronized void dispose() {
287         logger.info("Shutting down Velux Bridge '{}'.", getThing().getUID());
288         logger.trace("dispose(): shutting down continous refresh.");
289         // Just for avoidance of Potential null pointer access
290         ScheduledFuture<?> currentRefreshJob = refreshJob;
291         if (currentRefreshJob != null) {
292             logger.trace("dispose(): stopping the refresh.");
293             currentRefreshJob.cancel(true);
294         }
295         // Background execution of dispose
296         scheduler.execute(() -> {
297             logger.trace("dispose.scheduled(): (synchronous) logout initiated.");
298             thisBridge.bridgeLogout();
299             logger.trace("dispose.scheduled(): shutting down JSON bridge.");
300             myJsonBridge.shutdown();
301             logger.trace("dispose.scheduled(): shutting down SLIP bridge.");
302             mySlipBridge.shutdown();
303         });
304         logger.trace("dispose(): calling super class.");
305         super.dispose();
306         logger.trace("dispose() done.");
307     }
308
309     /**
310      * NOTE: It takes care by calling {@link #handleCommand} with the REFRESH command, that every used channel is
311      * initialized.
312      */
313     @Override
314     public void channelLinked(ChannelUID channelUID) {
315         if (thing.getStatus() == ThingStatus.ONLINE) {
316             channel2VeluxActuator.put(channelUID, new Thing2VeluxActuator(this, channelUID));
317             logger.trace("channelLinked({}) refreshing channel value with help of handleCommand as Thing is online.",
318                     channelUID.getAsString());
319             handleCommand(channelUID, RefreshType.REFRESH);
320         } else {
321             logger.trace("channelLinked({}) doing nothing as Thing is not online.", channelUID.getAsString());
322         }
323     }
324
325     @Override
326     public void channelUnlinked(ChannelUID channelUID) {
327         logger.trace("channelUnlinked({}) called.", channelUID.getAsString());
328     }
329
330     // Reconfiguration methods
331
332     private void bridgeParamsUpdated() {
333         logger.debug("bridgeParamsUpdated() called.");
334
335         // Determine the appropriate bridge communication channel
336         boolean validBridgeFound = false;
337         if (myJsonBridge.supportedProtocols.contains(veluxBridgeConfiguration.protocol)) {
338             logger.debug("bridgeParamsUpdated(): choosing JSON as communication method.");
339             thisBridge = myJsonBridge;
340             validBridgeFound = true;
341         }
342         if (mySlipBridge.supportedProtocols.contains(veluxBridgeConfiguration.protocol)) {
343             logger.debug("bridgeParamsUpdated(): choosing SLIP as communication method.");
344             thisBridge = mySlipBridge;
345             validBridgeFound = true;
346         }
347         if (!validBridgeFound) {
348             logger.debug("No valid protocol selected, aborting this {} binding.", VeluxBindingConstants.BINDING_ID);
349             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
350                     "@text/runtime.bridge-offline-no-valid-bridgeProtocol-selected");
351             logger.trace("bridgeParamsUpdated() done.");
352             return;
353         }
354
355         logger.trace("bridgeParamsUpdated(): Trying to authenticate towards bridge.");
356
357         if (!thisBridge.bridgeLogin()) {
358             logger.warn("{} bridge login sequence failed; expecting bridge is OFFLINE.",
359                     VeluxBindingConstants.BINDING_ID);
360             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
361                     "@text/runtime.bridge-offline-login-sequence-failed");
362             logger.trace("bridgeParamsUpdated() done.");
363             return;
364         }
365
366         logger.trace("bridgeParamsUpdated(): Querying bridge state.");
367         bridgeParameters.gateway = new VeluxBridgeDeviceStatus().retrieve(thisBridge);
368
369         logger.trace("bridgeParamsUpdated(): Fetching existing scenes.");
370         bridgeParameters.scenes.getScenes(thisBridge);
371         logger.info("Found {} scenes:\n\t{}", VeluxBindingConstants.BINDING_ID,
372                 bridgeParameters.scenes.getChannel().existingScenes.toString(false, "\n\t"));
373         logger.trace("bridgeParamsUpdated(): Fetching existing actuators/products.");
374         bridgeParameters.actuators.getProducts(thisBridge);
375         logger.info("Found {} actuators:\n\t{}", VeluxBindingConstants.BINDING_ID,
376                 bridgeParameters.actuators.getChannel().existingProducts.toString(false, "\n\t"));
377
378         if (thisBridge.bridgeAPI().setHouseStatusMonitor() != null) {
379             logger.trace("bridgeParamsUpdated(): Activating HouseStatusMonitor.");
380             if (new VeluxBridgeSetHouseStatusMonitor().modifyHSM(thisBridge, true)) {
381                 logger.trace("bridgeParamsUpdated(): HSM activated.");
382             } else {
383                 logger.warn("Activation of House-Status-Monitoring failed (might lead to a lack of status updates).");
384             }
385         }
386
387         veluxBridgeConfiguration.hasChanged = false;
388         logger.info("{} Bridge is online with {} scenes and {} actuators, now.", VeluxBindingConstants.BINDING_ID,
389                 bridgeParameters.scenes.getChannel().existingScenes.getNoMembers(),
390                 bridgeParameters.actuators.getChannel().existingProducts.getNoMembers());
391         logger.debug("Velux veluxBridge is online, now.");
392         updateStatus(ThingStatus.ONLINE);
393         logger.trace("bridgeParamsUpdated() successfully finished.");
394     }
395
396     // Continuous synchronization methods
397
398     private synchronized void refreshOpenHAB() {
399         logger.debug("refreshOpenHAB() initiated by {} starting cycle {}.", Thread.currentThread(), refreshCounter);
400
401         if (handleScheduler.isShutdown()) {
402             logger.trace("refreshOpenHAB(): handleScheduler is shutdown, recreating a scheduler pool.");
403             handleScheduler = ThreadPoolManager.getScheduledPool(VeluxBindingConstants.BINDING_ID);
404         }
405
406         logger.trace("refreshOpenHAB(): processing of possible HSM messages.");
407         // Background execution of bridge related I/O
408         handleScheduler.execute(() -> {
409             logger.trace("refreshOpenHAB.scheduled() initiated by {} will process HouseStatus.",
410                     Thread.currentThread());
411             if (new VeluxBridgeGetHouseStatus().evaluateState(thisBridge)) {
412                 logger.trace("refreshOpenHAB.scheduled(): successfully processed of GetHouseStatus()");
413             }
414             logger.trace("refreshOpenHAB.scheduled() initiated by {} has finished.", Thread.currentThread());
415         });
416
417         logger.trace(
418                 "refreshOpenHAB(): looping through all (both child things and bridge) linked channels for a need of refresh.");
419         for (ChannelUID channelUID : BridgeChannels.getAllLinkedChannelUIDs(this)) {
420             if (VeluxItemType.isToBeRefreshedNow(refreshCounter, thingTypeUIDOf(channelUID), channelUID.getId())) {
421                 logger.trace("refreshOpenHAB(): refreshing channel {}.", channelUID);
422                 handleCommand(channelUID, RefreshType.REFRESH);
423             }
424         }
425         logger.trace("refreshOpenHAB(): looping through properties for a need of refresh.");
426         for (VeluxItemType veluxItem : VeluxItemType.getPropertyEntriesByThing(getThing().getThingTypeUID())) {
427             if (VeluxItemType.isToBeRefreshedNow(refreshCounter, getThing().getThingTypeUID(),
428                     veluxItem.getIdentifier())) {
429                 logger.trace("refreshOpenHAB(): refreshing property {}.", veluxItem.getIdentifier());
430                 handleCommand(new ChannelUID(getThing().getUID(), veluxItem.getIdentifier()), RefreshType.REFRESH);
431             }
432         }
433         logger.debug("refreshOpenHAB() initiated by {} finished cycle {}.", Thread.currentThread(), refreshCounter);
434         refreshCounter++;
435     }
436
437     /**
438      * In case of recognized changes in the real world, the method will
439      * update the corresponding states via openHAB event bus.
440      */
441     private void syncChannelsWithProducts() {
442         if (!bridgeParameters.actuators.getChannel().existingProducts.isDirty()) {
443             return;
444         }
445         logger.trace("syncChannelsWithProducts(): there are some existing products with changed parameters.");
446         outer: for (VeluxProduct product : bridgeParameters.actuators.getChannel().existingProducts
447                 .valuesOfModified()) {
448             logger.trace("syncChannelsWithProducts(): actuator {} has changed values.", product.getProductName());
449             ProductBridgeIndex productPbi = product.getBridgeProductIndex();
450             logger.trace("syncChannelsWithProducts(): bridge index is {}.", productPbi);
451             for (ChannelUID channelUID : BridgeChannels.getAllLinkedChannelUIDs(this)) {
452                 if (!channel2VeluxActuator.containsKey(channelUID)) {
453                     logger.trace("syncChannelsWithProducts(): channel {} not found.", channelUID);
454                     continue;
455                 }
456                 if (!channel2VeluxActuator.get(channelUID).isKnown()) {
457                     logger.trace("syncChannelsWithProducts(): channel {} not registered on bridge.", channelUID);
458                     continue;
459                 }
460                 ProductBridgeIndex channelPbi = channel2VeluxActuator.get(channelUID).getProductBridgeIndex();
461                 if (!channelPbi.equals(productPbi)) {
462                     continue;
463                 }
464                 // Handle value inversion
465                 boolean isInverted = channel2VeluxActuator.get(channelUID).isInverted();
466                 logger.trace("syncChannelsWithProducts(): isInverted is {}.", isInverted);
467                 VeluxProductPosition position = new VeluxProductPosition(product.getCurrentPosition());
468                 if (position.isValid()) {
469                     PercentType positionAsPercent = position.getPositionAsPercentType(isInverted);
470                     logger.debug("syncChannelsWithProducts(): updating channel {} to position {}%.", channelUID,
471                             positionAsPercent);
472                     updateState(channelUID, positionAsPercent);
473                 } else {
474                     logger.trace("syncChannelsWithProducts(): update of channel {} to position {} skipped.", channelUID,
475                             position);
476                 }
477                 break outer;
478             }
479         }
480         logger.trace("syncChannelsWithProducts(): resetting dirty flag.");
481         bridgeParameters.actuators.getChannel().existingProducts.resetDirtyFlag();
482         logger.trace("syncChannelsWithProducts() done.");
483     }
484
485     // Processing of openHAB events
486
487     @Override
488     public void handleCommand(ChannelUID channelUID, Command command) {
489         logger.trace("handleCommand({}): command {} on channel {} will be scheduled.", Thread.currentThread(), command,
490                 channelUID.getAsString());
491         logger.debug("handleCommand({},{}) called.", channelUID.getAsString(), command);
492
493         // Background execution of bridge related I/O
494         handleScheduler.execute(() -> {
495             logger.trace("handleCommand.scheduled({}) Start work with calling handleCommandScheduled().",
496                     Thread.currentThread());
497             handleCommandScheduled(channelUID, command);
498             logger.trace("handleCommand.scheduled({}) done.", Thread.currentThread());
499         });
500         logger.trace("handleCommand({}) done.", Thread.currentThread());
501     }
502
503     /**
504      * Normally called by {@link #handleCommand} to handle a command for a given channel with possibly long execution
505      * time.
506      * <p>
507      * <B>NOTE:</B> This method is to be called as separated thread to ensure proper openHAB framework in parallel.
508      * <p>
509      *
510      * @param channelUID the {@link ChannelUID} of the channel to which the command was sent,
511      * @param command the {@link Command}.
512      */
513     private synchronized void handleCommandScheduled(ChannelUID channelUID, Command command) {
514         logger.trace("handleCommandScheduled({}): command {} on channel {}.", Thread.currentThread(), command,
515                 channelUID.getAsString());
516         logger.debug("handleCommandScheduled({},{}) called.", channelUID.getAsString(), command);
517
518         /*
519          * ===========================================================
520          * Common part
521          */
522
523         if (veluxBridgeConfiguration.isProtocolTraceEnabled) {
524             Threads.findDeadlocked();
525         }
526
527         String channelId = channelUID.getId();
528         State newState = null;
529         String itemName = channelUID.getAsString();
530         VeluxItemType itemType = VeluxItemType.getByThingAndChannel(thingTypeUIDOf(channelUID), channelUID.getId());
531
532         if (itemType == VeluxItemType.UNKNOWN) {
533             logger.warn("{} Cannot determine type of Channel {}, ignoring command {}.",
534                     VeluxBindingConstants.LOGGING_CONTACT, channelUID, command);
535             logger.trace("handleCommandScheduled() aborting.");
536             return;
537         }
538
539         // Build cache
540         if (!channel2VeluxActuator.containsKey(channelUID)) {
541             channel2VeluxActuator.put(channelUID, new Thing2VeluxActuator(this, channelUID));
542         }
543
544         if (veluxBridgeConfiguration.hasChanged) {
545             logger.trace("handleCommandScheduled(): work on updated bridge configuration parameters.");
546             bridgeParamsUpdated();
547         }
548
549         syncChannelsWithProducts();
550
551         if (command instanceof RefreshType) {
552             /*
553              * ===========================================================
554              * Refresh part
555              */
556             logger.trace("handleCommandScheduled(): work on refresh.");
557             if (!itemType.isReadable()) {
558                 logger.debug("handleCommandScheduled(): received a Refresh command for a non-readable item.");
559             } else {
560                 logger.trace("handleCommandScheduled(): refreshing item {} (type {}).", itemName, itemType);
561                 try { // expecting an IllegalArgumentException for unknown Velux device
562                     switch (itemType) {
563                         // Bridge channels
564                         case BRIDGE_STATUS:
565                             newState = ChannelBridgeStatus.handleRefresh(channelUID, channelId, this);
566                             break;
567                         case BRIDGE_DOWNTIME:
568                             newState = new DecimalType(
569                                     thisBridge.lastCommunication() - thisBridge.lastSuccessfulCommunication());
570                             break;
571                         case BRIDGE_FIRMWARE:
572                             newState = ChannelBridgeFirmware.handleRefresh(channelUID, channelId, this);
573                             break;
574                         case BRIDGE_IPADDRESS:
575                         case BRIDGE_SUBNETMASK:
576                         case BRIDGE_DEFAULTGW:
577                         case BRIDGE_DHCP:
578                             newState = ChannelBridgeLANconfig.handleRefresh(channelUID, channelId, this);
579                             break;
580                         case BRIDGE_WLANSSID:
581                         case BRIDGE_WLANPASSWORD:
582                             newState = ChannelBridgeWLANconfig.handleRefresh(channelUID, channelId, this);
583                             break;
584                         case BRIDGE_SCENES:
585                             newState = ChannelBridgeScenes.handleRefresh(channelUID, channelId, this);
586                             break;
587                         case BRIDGE_PRODUCTS:
588                             newState = ChannelBridgeProducts.handleRefresh(channelUID, channelId, this);
589                             break;
590                         case BRIDGE_CHECK:
591                             newState = ChannelBridgeCheck.handleRefresh(channelUID, channelId, this);
592                             break;
593                         // Actuator channels
594                         case ACTUATOR_POSITION:
595                         case ACTUATOR_STATE:
596                         case ROLLERSHUTTER_POSITION:
597                         case WINDOW_POSITION:
598                             newState = ChannelActuatorPosition.handleRefresh(channelUID, channelId, this);
599                             break;
600                         case ACTUATOR_LIMIT_MINIMUM:
601                         case ROLLERSHUTTER_LIMIT_MINIMUM:
602                         case WINDOW_LIMIT_MINIMUM:
603                             newState = ChannelActuatorLimitation.handleRefresh(channelUID, "", this);
604                             break;
605                         case ACTUATOR_LIMIT_MAXIMUM:
606                         case ROLLERSHUTTER_LIMIT_MAXIMUM:
607                         case WINDOW_LIMIT_MAXIMUM:
608                             newState = ChannelActuatorLimitation.handleRefresh(channelUID, channelId, this);
609                             break;
610
611                         // VirtualShutter channels
612                         case VSHUTTER_POSITION:
613                             newState = ChannelVShutterPosition.handleRefresh(channelUID, channelId, this);
614                             break;
615
616                         default:
617                             logger.trace(
618                                     "handleCommandScheduled(): cannot handle REFRESH on channel {} as it is of type {}.",
619                                     itemName, channelId);
620                     }
621                 } catch (IllegalArgumentException e) {
622                     logger.warn("Cannot handle REFRESH on channel {} as it isn't (yet) known to the bridge.", itemName);
623                 }
624                 if (newState != null) {
625                     if (itemType.isChannel()) {
626                         logger.debug("handleCommandScheduled(): updating channel {} to {}.", channelUID, newState);
627                         updateState(channelUID, newState);
628                     }
629                     if (itemType.isProperty()) {
630                         logger.debug("handleCommandScheduled(): updating property {} to {}.", channelUID, newState);
631                         ThingProperty.setValue(this, itemType.getIdentifier(), newState.toString());
632
633                     }
634                 } else {
635                     logger.info("handleCommandScheduled({},{}): updating of item {} (type {}) failed.",
636                             channelUID.getAsString(), command, itemName, itemType);
637                 }
638             }
639         } else {
640             /*
641              * ===========================================================
642              * Modification part
643              */
644             logger.trace("handleCommandScheduled(): working on item {} (type {}) with COMMAND {}.", itemName, itemType,
645                     command);
646             Command newValue = null;
647             try { // expecting an IllegalArgumentException for unknown Velux device
648                 switch (itemType) {
649                     // Bridge channels
650                     case BRIDGE_RELOAD:
651                         if (command == OnOffType.ON) {
652                             logger.trace("handleCommandScheduled(): about to reload informations from veluxBridge.");
653                             bridgeParamsUpdated();
654                         } else {
655                             logger.trace("handleCommandScheduled(): ignoring OFF command.");
656                         }
657                         break;
658                     case BRIDGE_DO_DETECTION:
659                         ChannelBridgeDoDetection.handleCommand(channelUID, channelId, command, this);
660                         break;
661
662                     // Scene channels
663                     case SCENE_ACTION:
664                         ChannelSceneAction.handleCommand(channelUID, channelId, command, this);
665                         break;
666                     case SCENE_SILENTMODE:
667                         ChannelSceneSilentmode.handleCommand(channelUID, channelId, command, this);
668                         break;
669
670                     // Actuator channels
671                     case ACTUATOR_POSITION:
672                     case ACTUATOR_STATE:
673                     case ROLLERSHUTTER_POSITION:
674                     case WINDOW_POSITION:
675                         ChannelActuatorPosition.handleCommand(channelUID, channelId, command, this);
676                         break;
677                     case ACTUATOR_LIMIT_MINIMUM:
678                     case ROLLERSHUTTER_LIMIT_MINIMUM:
679                     case WINDOW_LIMIT_MINIMUM:
680                         ChannelActuatorLimitation.handleCommand(channelUID, channelId, command, this);
681                         break;
682                     case ACTUATOR_LIMIT_MAXIMUM:
683                     case ROLLERSHUTTER_LIMIT_MAXIMUM:
684                     case WINDOW_LIMIT_MAXIMUM:
685                         ChannelActuatorLimitation.handleCommand(channelUID, channelId, command, this);
686                         break;
687
688                     // VirtualShutter channels
689                     case VSHUTTER_POSITION:
690                         newValue = ChannelVShutterPosition.handleCommand(channelUID, channelId, command, this);
691                         break;
692
693                     default:
694                         logger.warn("{} Cannot handle command {} on channel {} (type {}).",
695                                 VeluxBindingConstants.LOGGING_CONTACT, command, itemName, itemType);
696                 }
697             } catch (IllegalArgumentException e) {
698                 logger.warn("Cannot handle command on channel {} as it isn't (yet) known to the bridge.", itemName);
699             }
700             if (newValue != null) {
701                 postCommand(channelUID, newValue);
702             }
703         }
704         ThingProperty.setValue(this, VeluxBindingConstants.PROPERTY_BRIDGE_TIMESTAMP_ATTEMPT,
705                 new java.util.Date(thisBridge.lastCommunication()).toString());
706         ThingProperty.setValue(this, VeluxBindingConstants.PROPERTY_BRIDGE_TIMESTAMP_SUCCESS,
707                 new java.util.Date(thisBridge.lastSuccessfulCommunication()).toString());
708         logger.trace("handleCommandScheduled({}) done.", Thread.currentThread());
709     }
710 }