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