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