2 * Copyright (c) 2010-2021 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.velux.internal.handler;
15 import java.util.Collection;
16 import java.util.Collections;
18 import java.util.concurrent.ConcurrentHashMap;
19 import java.util.concurrent.ExecutorService;
20 import java.util.concurrent.Executors;
21 import java.util.concurrent.ScheduledFuture;
22 import java.util.concurrent.TimeUnit;
24 import org.eclipse.jdt.annotation.NonNullByDefault;
25 import org.eclipse.jdt.annotation.Nullable;
26 import org.openhab.binding.velux.internal.VeluxBinding;
27 import org.openhab.binding.velux.internal.VeluxBindingConstants;
28 import org.openhab.binding.velux.internal.VeluxItemType;
29 import org.openhab.binding.velux.internal.action.VeluxActions;
30 import org.openhab.binding.velux.internal.bridge.VeluxBridge;
31 import org.openhab.binding.velux.internal.bridge.VeluxBridgeActuators;
32 import org.openhab.binding.velux.internal.bridge.VeluxBridgeDeviceStatus;
33 import org.openhab.binding.velux.internal.bridge.VeluxBridgeGetFirmware;
34 import org.openhab.binding.velux.internal.bridge.VeluxBridgeGetHouseStatus;
35 import org.openhab.binding.velux.internal.bridge.VeluxBridgeInstance;
36 import org.openhab.binding.velux.internal.bridge.VeluxBridgeLANConfig;
37 import org.openhab.binding.velux.internal.bridge.VeluxBridgeProvider;
38 import org.openhab.binding.velux.internal.bridge.VeluxBridgeScenes;
39 import org.openhab.binding.velux.internal.bridge.VeluxBridgeSetHouseStatusMonitor;
40 import org.openhab.binding.velux.internal.bridge.VeluxBridgeWLANConfig;
41 import org.openhab.binding.velux.internal.bridge.common.BridgeAPI;
42 import org.openhab.binding.velux.internal.bridge.common.BridgeCommunicationProtocol;
43 import org.openhab.binding.velux.internal.bridge.common.RunProductCommand;
44 import org.openhab.binding.velux.internal.bridge.common.RunReboot;
45 import org.openhab.binding.velux.internal.bridge.json.JsonVeluxBridge;
46 import org.openhab.binding.velux.internal.bridge.slip.SlipVeluxBridge;
47 import org.openhab.binding.velux.internal.config.VeluxBridgeConfiguration;
48 import org.openhab.binding.velux.internal.development.Threads;
49 import org.openhab.binding.velux.internal.handler.utils.ExtendedBaseBridgeHandler;
50 import org.openhab.binding.velux.internal.handler.utils.Thing2VeluxActuator;
51 import org.openhab.binding.velux.internal.handler.utils.ThingProperty;
52 import org.openhab.binding.velux.internal.things.VeluxExistingProducts;
53 import org.openhab.binding.velux.internal.things.VeluxExistingScenes;
54 import org.openhab.binding.velux.internal.things.VeluxProduct;
55 import org.openhab.binding.velux.internal.things.VeluxProduct.ProductBridgeIndex;
56 import org.openhab.binding.velux.internal.things.VeluxProductPosition;
57 import org.openhab.binding.velux.internal.utils.Localization;
58 import org.openhab.core.common.AbstractUID;
59 import org.openhab.core.common.NamedThreadFactory;
60 import org.openhab.core.library.types.DecimalType;
61 import org.openhab.core.library.types.OnOffType;
62 import org.openhab.core.library.types.PercentType;
63 import org.openhab.core.thing.Bridge;
64 import org.openhab.core.thing.ChannelUID;
65 import org.openhab.core.thing.ThingStatus;
66 import org.openhab.core.thing.ThingStatusDetail;
67 import org.openhab.core.thing.ThingTypeUID;
68 import org.openhab.core.thing.binding.ThingHandlerService;
69 import org.openhab.core.types.Command;
70 import org.openhab.core.types.RefreshType;
71 import org.openhab.core.types.State;
72 import org.openhab.core.types.UnDefType;
73 import org.slf4j.Logger;
74 import org.slf4j.LoggerFactory;
77 * <B>Common interaction with the </B><I>Velux</I><B> bridge.</B>
79 * It implements the communication between <B>OpenHAB</B> and the <I>Velux</I> Bridge:
81 * <LI><B>OpenHAB</B> Event Bus → <I>Velux</I> <B>bridge</B>
83 * Sending commands and value updates.</LI>
86 * <LI><I>Velux</I> <B>bridge</B> → <B>OpenHAB</B>:
88 * Retrieving information by sending a Refresh command.</LI>
91 * Entry point for this class is the method
92 * {@link VeluxBridgeHandler#handleCommand handleCommand}.
94 * @author Guenther Schreiner - Initial contribution.
97 public class VeluxBridgeHandler extends ExtendedBaseBridgeHandler implements VeluxBridgeInstance, VeluxBridgeProvider {
99 private final Logger logger = LoggerFactory.getLogger(VeluxBridgeHandler.class);
104 * Scheduler for continuous refresh by scheduleWithFixedDelay.
106 private @Nullable ScheduledFuture<?> refreshJob = null;
109 * Counter of refresh invocations by {@link refreshJob}.
111 private int refreshCounter = 0;
114 * Dedicated task executor for the long-running bridge communication tasks.
116 * Note: there is no point in using multi threaded thread-pool here, since all the submitted (Runnable) tasks are
117 * anyway forced to go through the same serial pipeline, because they all call the same class level "synchronized"
118 * method to actually communicate with the KLF bridge via its one single TCP socket connection
120 private @Nullable ExecutorService taskExecutor = null;
121 private @Nullable NamedThreadFactory threadFactory = null;
123 private VeluxBridge myJsonBridge = new JsonVeluxBridge(this);
124 private VeluxBridge mySlipBridge = new SlipVeluxBridge(this);
127 * **************************************
128 * ***** Default visibility Objects *****
131 VeluxBridge thisBridge = myJsonBridge;
132 public BridgeParameters bridgeParameters = new BridgeParameters();
133 Localization localization;
136 * Mapping from ChannelUID to class Thing2VeluxActuator, which return Velux device information, probably cached.
138 Map<ChannelUID, Thing2VeluxActuator> channel2VeluxActuator = new ConcurrentHashMap<>();
141 * Information retrieved by {@link VeluxBinding#VeluxBinding}.
143 private VeluxBridgeConfiguration veluxBridgeConfiguration = new VeluxBridgeConfiguration();
146 * ************************
147 * ***** Constructors *****
150 public VeluxBridgeHandler(final Bridge bridge, Localization localization) {
152 logger.trace("VeluxBridgeHandler(constructor with bridge={}, localization={}) called.", bridge, localization);
153 this.localization = localization;
154 logger.debug("Creating a VeluxBridgeHandler for thing '{}'.", getThing().getUID());
161 * Set of information retrieved from the bridge/gateway:
164 * <LI>{@link #actuators} - Already known actuators,</LI>
165 * <LI>{@link #scenes} - Already on the gateway defined scenes,</LI>
166 * <LI>{@link #gateway} - Current status of the gateway status,</LI>
167 * <LI>{@link #firmware} - Information about the gateway firmware revision,</LI>
168 * <LI>{@link #lanConfig} - Information about the gateway configuration,</LI>
169 * <LI>{@link #wlanConfig} - Information about the gateway configuration.</LI>
172 public class BridgeParameters {
173 /** Information retrieved by {@link VeluxBridgeActuators#getProducts} */
174 public VeluxBridgeActuators actuators = new VeluxBridgeActuators();
176 /** Information retrieved by {@link org.openhab.binding.velux.internal.bridge.VeluxBridgeScenes#getScenes} */
177 VeluxBridgeScenes scenes = new VeluxBridgeScenes();
179 /** Information retrieved by {@link VeluxBridgeDeviceStatus#retrieve} */
180 VeluxBridgeDeviceStatus.Channel gateway = new VeluxBridgeDeviceStatus().getChannel();
182 /** Information retrieved by {@link VeluxBridgeGetFirmware#retrieve} */
183 VeluxBridgeGetFirmware.Channel firmware = new VeluxBridgeGetFirmware().getChannel();
185 /** Information retrieved by {@link VeluxBridgeLANConfig#retrieve} */
186 VeluxBridgeLANConfig.Channel lanConfig = new VeluxBridgeLANConfig().getChannel();
188 /** Information retrieved by {@link VeluxBridgeWLANConfig#retrieve} */
189 VeluxBridgeWLANConfig.Channel wlanConfig = new VeluxBridgeWLANConfig().getChannel();
195 * Provide the ThingType for a given Channel.
197 * Separated into this private method to deal with the deprecated method.
200 * @param channelUID for type {@link ChannelUID}.
201 * @return thingTypeUID of type {@link ThingTypeUID}.
203 public ThingTypeUID thingTypeUIDOf(ChannelUID channelUID) {
204 String[] segments = channelUID.getAsString().split(AbstractUID.SEPARATOR);
205 if (segments.length > 1) {
206 return new ThingTypeUID(segments[0], segments[1]);
208 logger.warn("thingTypeUIDOf({}) failed.", channelUID);
209 return new ThingTypeUID(VeluxBindingConstants.BINDING_ID, VeluxBindingConstants.UNKNOWN_THING_TYPE_ID);
212 // Objects and Methods for interface VeluxBridgeInstance
215 * Information retrieved by ...
218 public VeluxBridgeConfiguration veluxBridgeConfiguration() {
219 return veluxBridgeConfiguration;
223 * Information retrieved by {@link VeluxBridgeActuators#getProducts}
226 public VeluxExistingProducts existingProducts() {
227 return bridgeParameters.actuators.getChannel().existingProducts;
231 * Information retrieved by {@link VeluxBridgeScenes#getScenes}
234 public VeluxExistingScenes existingScenes() {
235 return bridgeParameters.scenes.getChannel().existingScenes;
238 // Objects and Methods for interface VeluxBridgeProvider *****
241 public boolean bridgeCommunicate(BridgeCommunicationProtocol communication) {
242 logger.warn("bridgeCommunicate() called. Should never be called (as implemented by protocol-specific layers).");
247 public @Nullable BridgeAPI bridgeAPI() {
248 logger.warn("bridgeAPI() called. Should never be called (as implemented by protocol-specific layers).");
252 // Provisioning/Deprovisioning methods *****
255 public void initialize() {
256 logger.info("Initializing Velux Bridge '{}'.", getThing().getUID());
257 // The framework requires you to return from this method quickly.
258 // Setting the thing status to UNKNOWN temporarily and let the background task decide for the real status.
259 logger.trace("initialize() called.");
260 updateStatus(ThingStatus.UNKNOWN);
261 // Take care of unusual situations...
262 if (scheduler.isShutdown()) {
263 logger.warn("initialize(): scheduler is shutdown, aborting the initialization of this bridge.");
267 logger.trace("initialize(): preparing background initialization task.");
268 // Background initialization...
269 scheduler.execute(() -> {
270 logger.trace("initialize.scheduled(): Further work within scheduler.execute().");
271 logger.trace("initialize.scheduled(): Initializing bridge configuration parameters.");
272 this.veluxBridgeConfiguration = new VeluxBinding(getConfigAs(VeluxBridgeConfiguration.class)).checked();
273 logger.trace("initialize.scheduled(): work on updated bridge configuration parameters.");
274 bridgeParamsUpdated();
276 logger.debug("initialize.scheduled(): activated scheduler with {} milliseconds.",
277 this.veluxBridgeConfiguration.refreshMSecs);
278 refreshJob = scheduler.scheduleWithFixedDelay(() -> {
281 } catch (RuntimeException e) {
282 logger.warn("Exception occurred during activated refresh scheduler: {}.", e.getMessage());
284 }, this.veluxBridgeConfiguration.refreshMSecs, this.veluxBridgeConfiguration.refreshMSecs,
285 TimeUnit.MILLISECONDS);
286 logger.trace("initialize.scheduled(): done.");
288 logger.trace("initialize() done.");
292 * NOTE: It takes care about shutting down the connections before removal of this binding.
295 public synchronized void dispose() {
296 logger.info("Shutting down Velux Bridge '{}'.", getThing().getUID());
297 logger.trace("dispose(): shutting down continous refresh.");
298 // Just for avoidance of Potential null pointer access
299 ScheduledFuture<?> currentRefreshJob = refreshJob;
300 if (currentRefreshJob != null) {
301 logger.trace("dispose(): stopping the refresh.");
302 currentRefreshJob.cancel(true);
304 // shut down the task executor
305 ExecutorService taskExecutor = this.taskExecutor;
306 if (taskExecutor != null) {
307 taskExecutor.shutdownNow();
309 // Background execution of dispose
310 scheduler.execute(() -> {
311 logger.trace("dispose.scheduled(): (synchronous) logout initiated.");
312 thisBridge.bridgeLogout();
313 logger.trace("dispose.scheduled(): shutting down JSON bridge.");
314 myJsonBridge.shutdown();
315 logger.trace("dispose.scheduled(): shutting down SLIP bridge.");
316 mySlipBridge.shutdown();
318 logger.trace("dispose(): calling super class.");
320 logger.trace("dispose() done.");
324 * NOTE: It takes care by calling {@link #handleCommand} with the REFRESH command, that every used channel is
328 public void channelLinked(ChannelUID channelUID) {
329 if (thing.getStatus() == ThingStatus.ONLINE) {
330 channel2VeluxActuator.put(channelUID, new Thing2VeluxActuator(this, channelUID));
331 logger.trace("channelLinked({}) refreshing channel value with help of handleCommand as Thing is online.",
332 channelUID.getAsString());
333 handleCommand(channelUID, RefreshType.REFRESH);
335 logger.trace("channelLinked({}) doing nothing as Thing is not online.", channelUID.getAsString());
340 public void channelUnlinked(ChannelUID channelUID) {
341 logger.trace("channelUnlinked({}) called.", channelUID.getAsString());
344 // Reconfiguration methods
346 private void bridgeParamsUpdated() {
347 logger.debug("bridgeParamsUpdated() called.");
349 // Determine the appropriate bridge communication channel
350 boolean validBridgeFound = false;
351 if (myJsonBridge.supportedProtocols.contains(veluxBridgeConfiguration.protocol)) {
352 logger.debug("bridgeParamsUpdated(): choosing JSON as communication method.");
353 thisBridge = myJsonBridge;
354 validBridgeFound = true;
356 if (mySlipBridge.supportedProtocols.contains(veluxBridgeConfiguration.protocol)) {
357 logger.debug("bridgeParamsUpdated(): choosing SLIP as communication method.");
358 thisBridge = mySlipBridge;
359 validBridgeFound = true;
361 if (!validBridgeFound) {
362 logger.debug("No valid protocol selected, aborting this {} binding.", VeluxBindingConstants.BINDING_ID);
363 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
364 "@text/runtime.bridge-offline-no-valid-bridgeProtocol-selected");
365 logger.trace("bridgeParamsUpdated() done.");
369 logger.trace("bridgeParamsUpdated(): Trying to authenticate towards bridge.");
371 if (!thisBridge.bridgeLogin()) {
372 logger.warn("{} bridge login sequence failed; expecting bridge is OFFLINE.",
373 VeluxBindingConstants.BINDING_ID);
374 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
375 "@text/runtime.bridge-offline-login-sequence-failed");
376 logger.trace("bridgeParamsUpdated() done.");
380 logger.trace("bridgeParamsUpdated(): Querying bridge state.");
381 bridgeParameters.gateway = new VeluxBridgeDeviceStatus().retrieve(thisBridge);
383 logger.trace("bridgeParamsUpdated(): Fetching existing scenes.");
384 bridgeParameters.scenes.getScenes(thisBridge);
385 logger.info("Found {} scenes:\n\t{}", VeluxBindingConstants.BINDING_ID,
386 bridgeParameters.scenes.getChannel().existingScenes.toString(false, "\n\t"));
387 logger.trace("bridgeParamsUpdated(): Fetching existing actuators/products.");
388 bridgeParameters.actuators.getProducts(thisBridge);
389 logger.info("Found {} actuators:\n\t{}", VeluxBindingConstants.BINDING_ID,
390 bridgeParameters.actuators.getChannel().existingProducts.toString(false, "\n\t"));
392 if (thisBridge.bridgeAPI().setHouseStatusMonitor() != null) {
393 logger.trace("bridgeParamsUpdated(): Activating HouseStatusMonitor.");
394 if (new VeluxBridgeSetHouseStatusMonitor().modifyHSM(thisBridge, true)) {
395 logger.trace("bridgeParamsUpdated(): HSM activated.");
397 logger.warn("Activation of House-Status-Monitoring failed (might lead to a lack of status updates).");
401 veluxBridgeConfiguration.hasChanged = false;
402 logger.info("{} Bridge is online with {} scenes and {} actuators, now.", VeluxBindingConstants.BINDING_ID,
403 bridgeParameters.scenes.getChannel().existingScenes.getNoMembers(),
404 bridgeParameters.actuators.getChannel().existingProducts.getNoMembers());
405 logger.debug("Velux veluxBridge is online, now.");
406 updateStatus(ThingStatus.ONLINE);
407 logger.trace("bridgeParamsUpdated() successfully finished.");
410 // Continuous synchronization methods
412 private synchronized void refreshOpenHAB() {
413 logger.debug("refreshOpenHAB() initiated by {} starting cycle {}.", Thread.currentThread(), refreshCounter);
414 logger.trace("refreshOpenHAB(): processing of possible HSM messages.");
416 // Background execution of bridge related I/O
417 getTaskExecutor().execute(() -> {
418 logger.trace("refreshOpenHAB.scheduled() initiated by {} will process HouseStatus.",
419 Thread.currentThread());
420 if (new VeluxBridgeGetHouseStatus().evaluateState(thisBridge)) {
421 logger.trace("refreshOpenHAB.scheduled(): => GetHouseStatus() => updates received => synchronizing");
422 syncChannelsWithProducts();
424 logger.trace("refreshOpenHAB.scheduled(): => GetHouseStatus() => no updates");
426 logger.trace("refreshOpenHAB.scheduled() initiated by {} has finished.", Thread.currentThread());
429 logger.trace("refreshOpenHAB(): loop through all (child things and bridge) linked channels needing a refresh");
430 for (ChannelUID channelUID : BridgeChannels.getAllLinkedChannelUIDs(this)) {
431 if (VeluxItemType.isToBeRefreshedNow(refreshCounter, thingTypeUIDOf(channelUID), channelUID.getId())) {
432 logger.trace("refreshOpenHAB(): refreshing channel {}.", channelUID);
433 handleCommand(channelUID, RefreshType.REFRESH);
437 logger.trace("refreshOpenHAB(): loop through properties needing a refresh");
438 for (VeluxItemType veluxItem : VeluxItemType.getPropertyEntriesByThing(getThing().getThingTypeUID())) {
439 if (VeluxItemType.isToBeRefreshedNow(refreshCounter, getThing().getThingTypeUID(),
440 veluxItem.getIdentifier())) {
441 logger.trace("refreshOpenHAB(): refreshing property {}.", veluxItem.getIdentifier());
442 handleCommand(new ChannelUID(getThing().getUID(), veluxItem.getIdentifier()), RefreshType.REFRESH);
445 logger.debug("refreshOpenHAB() initiated by {} finished cycle {}.", Thread.currentThread(), refreshCounter);
450 * In case of recognized changes in the real world, the method will
451 * update the corresponding states via openHAB event bus.
453 private void syncChannelsWithProducts() {
454 if (!bridgeParameters.actuators.getChannel().existingProducts.isDirty()) {
455 logger.trace("syncChannelsWithProducts(): no existing products with changed parameters.");
458 logger.trace("syncChannelsWithProducts(): there are some existing products with changed parameters.");
459 for (VeluxProduct product : bridgeParameters.actuators.getChannel().existingProducts.valuesOfModified()) {
460 logger.trace("syncChannelsWithProducts(): actuator {} has changed values.", product.getProductName());
461 ProductBridgeIndex productPbi = product.getBridgeProductIndex();
462 logger.trace("syncChannelsWithProducts(): bridge index is {}.", productPbi);
463 for (ChannelUID channelUID : BridgeChannels.getAllLinkedChannelUIDs(this)) {
464 if (!channel2VeluxActuator.containsKey(channelUID)) {
465 logger.trace("syncChannelsWithProducts(): channel {} not found.", channelUID);
468 Thing2VeluxActuator actuator = channel2VeluxActuator.get(channelUID);
469 if (actuator == null || !actuator.isKnown()) {
470 logger.trace("syncChannelsWithProducts(): channel {} not registered on bridge.", channelUID);
473 ProductBridgeIndex channelPbi = actuator.getProductBridgeIndex();
474 if (!channelPbi.equals(productPbi)) {
477 // Handle value inversion
478 boolean isInverted = actuator.isInverted();
479 logger.trace("syncChannelsWithProducts(): isInverted is {}.", isInverted);
480 VeluxProductPosition position = new VeluxProductPosition(product.getDisplayPosition());
481 if (position.isValid()) {
482 PercentType positionAsPercent = position.getPositionAsPercentType(isInverted);
483 logger.debug("syncChannelsWithProducts(): updating channel {} to position {}%.", channelUID,
485 updateState(channelUID, positionAsPercent);
488 logger.trace("syncChannelsWithProducts(): update channel {} to 'UNDEFINED'.", channelUID);
489 updateState(channelUID, UnDefType.UNDEF);
493 logger.trace("syncChannelsWithProducts(): resetting dirty flag.");
494 bridgeParameters.actuators.getChannel().existingProducts.resetDirtyFlag();
495 logger.trace("syncChannelsWithProducts() done.");
498 // Processing of openHAB events
501 public void handleCommand(ChannelUID channelUID, Command command) {
502 logger.trace("handleCommand({}): command {} on channel {} will be scheduled.", Thread.currentThread(), command,
503 channelUID.getAsString());
504 logger.debug("handleCommand({},{}) called.", channelUID.getAsString(), command);
506 // Background execution of bridge related I/O
507 getTaskExecutor().execute(() -> {
508 logger.trace("handleCommand.scheduled({}) Start work with calling handleCommandScheduled().",
509 Thread.currentThread());
510 handleCommandScheduled(channelUID, command);
511 logger.trace("handleCommand.scheduled({}) done.", Thread.currentThread());
513 logger.trace("handleCommand({}) done.", Thread.currentThread());
517 * Normally called by {@link #handleCommand} to handle a command for a given channel with possibly long execution
520 * <B>NOTE:</B> This method is to be called as separated thread to ensure proper openHAB framework in parallel.
523 * @param channelUID the {@link ChannelUID} of the channel to which the command was sent,
524 * @param command the {@link Command}.
526 private synchronized void handleCommandScheduled(ChannelUID channelUID, Command command) {
527 logger.trace("handleCommandScheduled({}): command {} on channel {}.", Thread.currentThread(), command,
528 channelUID.getAsString());
529 logger.debug("handleCommandScheduled({},{}) called.", channelUID.getAsString(), command);
532 * ===========================================================
536 if (veluxBridgeConfiguration.isProtocolTraceEnabled) {
537 Threads.findDeadlocked();
540 String channelId = channelUID.getId();
541 State newState = null;
542 String itemName = channelUID.getAsString();
543 VeluxItemType itemType = VeluxItemType.getByThingAndChannel(thingTypeUIDOf(channelUID), channelUID.getId());
545 if (itemType == VeluxItemType.UNKNOWN) {
546 logger.warn("{} Cannot determine type of Channel {}, ignoring command {}.",
547 VeluxBindingConstants.LOGGING_CONTACT, channelUID, command);
548 logger.trace("handleCommandScheduled() aborting.");
553 if (!channel2VeluxActuator.containsKey(channelUID)) {
554 channel2VeluxActuator.put(channelUID, new Thing2VeluxActuator(this, channelUID));
557 if (veluxBridgeConfiguration.hasChanged) {
558 logger.trace("handleCommandScheduled(): work on updated bridge configuration parameters.");
559 bridgeParamsUpdated();
562 syncChannelsWithProducts();
564 if (command instanceof RefreshType) {
566 * ===========================================================
569 logger.trace("handleCommandScheduled(): work on refresh.");
570 if (!itemType.isReadable()) {
571 logger.debug("handleCommandScheduled(): received a Refresh command for a non-readable item.");
573 logger.trace("handleCommandScheduled(): refreshing item {} (type {}).", itemName, itemType);
574 try { // expecting an IllegalArgumentException for unknown Velux device
578 newState = ChannelBridgeStatus.handleRefresh(channelUID, channelId, this);
580 case BRIDGE_DOWNTIME:
581 newState = new DecimalType(
582 thisBridge.lastCommunication() - thisBridge.lastSuccessfulCommunication());
584 case BRIDGE_FIRMWARE:
585 newState = ChannelBridgeFirmware.handleRefresh(channelUID, channelId, this);
588 // delete legacy property name entry (if any) and fall through
589 ThingProperty.setValue(this, VeluxBridgeConfiguration.BRIDGE_IPADDRESS, null);
590 case BRIDGE_SUBNETMASK:
591 case BRIDGE_DEFAULTGW:
593 newState = ChannelBridgeLANconfig.handleRefresh(channelUID, channelId, this);
595 case BRIDGE_WLANSSID:
596 case BRIDGE_WLANPASSWORD:
597 newState = ChannelBridgeWLANconfig.handleRefresh(channelUID, channelId, this);
600 newState = ChannelBridgeScenes.handleRefresh(channelUID, channelId, this);
602 case BRIDGE_PRODUCTS:
603 newState = ChannelBridgeProducts.handleRefresh(channelUID, channelId, this);
606 newState = ChannelBridgeCheck.handleRefresh(channelUID, channelId, this);
609 case ACTUATOR_POSITION:
611 case ROLLERSHUTTER_POSITION:
612 case WINDOW_POSITION:
613 newState = ChannelActuatorPosition.handleRefresh(channelUID, channelId, this);
615 case ACTUATOR_LIMIT_MINIMUM:
616 case ROLLERSHUTTER_LIMIT_MINIMUM:
617 case WINDOW_LIMIT_MINIMUM:
618 // note: the empty string ("") below is intentional
619 newState = ChannelActuatorLimitation.handleRefresh(channelUID, "", this);
621 case ACTUATOR_LIMIT_MAXIMUM:
622 case ROLLERSHUTTER_LIMIT_MAXIMUM:
623 case WINDOW_LIMIT_MAXIMUM:
624 newState = ChannelActuatorLimitation.handleRefresh(channelUID, channelId, this);
627 // VirtualShutter channels
628 case VSHUTTER_POSITION:
629 newState = ChannelVShutterPosition.handleRefresh(channelUID, channelId, this);
634 "handleCommandScheduled(): cannot handle REFRESH on channel {} as it is of type {}.",
635 itemName, channelId);
637 } catch (IllegalArgumentException e) {
638 logger.warn("Cannot handle REFRESH on channel {} as it isn't (yet) known to the bridge.", itemName);
640 if (newState != null) {
641 if (itemType.isChannel()) {
642 logger.debug("handleCommandScheduled(): updating channel {} to {}.", channelUID, newState);
643 updateState(channelUID, newState);
644 } else if (itemType.isProperty()) {
645 // if property value is 'unknown', null it completely
646 String val = newState.toString();
647 if (VeluxBindingConstants.UNKNOWN.equals(val)) {
650 logger.debug("handleCommandScheduled(): updating property {} to {}.", channelUID, val);
651 ThingProperty.setValue(this, itemType.getIdentifier(), val);
654 logger.info("handleCommandScheduled({},{}): updating of item {} (type {}) failed.",
655 channelUID.getAsString(), command, itemName, itemType);
660 * ===========================================================
663 logger.trace("handleCommandScheduled(): working on item {} (type {}) with COMMAND {}.", itemName, itemType,
665 Command newValue = null;
666 try { // expecting an IllegalArgumentException for unknown Velux device
670 if (command == OnOffType.ON) {
671 logger.trace("handleCommandScheduled(): about to reload informations from veluxBridge.");
672 bridgeParamsUpdated();
674 logger.trace("handleCommandScheduled(): ignoring OFF command.");
677 case BRIDGE_DO_DETECTION:
678 ChannelBridgeDoDetection.handleCommand(channelUID, channelId, command, this);
683 ChannelSceneAction.handleCommand(channelUID, channelId, command, this);
687 * NOTA BENE: Setting of a scene silent mode is no longer supported via the KLF API (i.e. the
688 * GW_SET_NODE_VELOCITY_REQ/CFM command set is no longer supported in the API), so the binding can
689 * no longer explicitly support a Channel with such a function. Therefore the silent mode Channel
690 * type was removed from the binding implementation.
692 * By contrast scene actions can still be called with a silent mode argument, so a silent mode
693 * Configuration Parameter has been introduced as a means for the user to set this argument.
695 * Strictly speaking the following case statement will now never be called, so in theory it,
696 * AND ALL THE CLASSES BEHIND, could be deleted from the binding CODE BASE. But out of prudence
697 * it is retained anyway 'just in case'.
699 case SCENE_SILENTMODE:
700 ChannelSceneSilentmode.handleCommand(channelUID, channelId, command, this);
704 case ACTUATOR_POSITION:
706 case ROLLERSHUTTER_POSITION:
707 case WINDOW_POSITION:
708 newValue = ChannelActuatorPosition.handleCommand(channelUID, channelId, command, this);
710 case ACTUATOR_LIMIT_MINIMUM:
711 case ROLLERSHUTTER_LIMIT_MINIMUM:
712 case WINDOW_LIMIT_MINIMUM:
713 ChannelActuatorLimitation.handleCommand(channelUID, channelId, command, this);
715 case ACTUATOR_LIMIT_MAXIMUM:
716 case ROLLERSHUTTER_LIMIT_MAXIMUM:
717 case WINDOW_LIMIT_MAXIMUM:
718 ChannelActuatorLimitation.handleCommand(channelUID, channelId, command, this);
721 // VirtualShutter channels
722 case VSHUTTER_POSITION:
723 newValue = ChannelVShutterPosition.handleCommand(channelUID, channelId, command, this);
727 logger.warn("{} Cannot handle command {} on channel {} (type {}).",
728 VeluxBindingConstants.LOGGING_CONTACT, command, itemName, itemType);
730 } catch (IllegalArgumentException e) {
731 logger.warn("Cannot handle command on channel {} as it isn't (yet) known to the bridge.", itemName);
733 if (newValue != null) {
734 postCommand(channelUID, newValue);
737 ThingProperty.setValue(this, VeluxBindingConstants.PROPERTY_BRIDGE_TIMESTAMP_ATTEMPT,
738 new java.util.Date(thisBridge.lastCommunication()).toString());
739 ThingProperty.setValue(this, VeluxBindingConstants.PROPERTY_BRIDGE_TIMESTAMP_SUCCESS,
740 new java.util.Date(thisBridge.lastSuccessfulCommunication()).toString());
741 logger.trace("handleCommandScheduled({}) done.", Thread.currentThread());
745 * Register the exported actions
748 public Collection<Class<? extends ThingHandlerService>> getServices() {
749 return Collections.singletonList(VeluxActions.class);
753 * Exported method (called by an OpenHAB Rules Action) to issue a reboot command to the hub.
755 * @return true if the command could be issued
757 public boolean runReboot() {
758 logger.trace("runReboot() called on {}", getThing().getUID());
759 RunReboot bcp = thisBridge.bridgeAPI().runReboot();
761 // background execution of reboot process
762 getTaskExecutor().execute(() -> {
763 if (thisBridge.bridgeCommunicate(bcp)) {
764 logger.info("Reboot command {}sucessfully sent to {}", bcp.isCommunicationSuccessful() ? "" : "un",
765 getThing().getUID());
774 * Exported method (called by an OpenHAB Rules Action) to move an actuator relative to its current position
776 * @param nodeId the node to be moved
777 * @param relativePercent relative position change to the current position (-100% <= relativePercent <= +100%)
778 * @return true if the command could be issued
780 public boolean moveRelative(int nodeId, int relativePercent) {
781 logger.trace("moveRelative() called on {}", getThing().getUID());
782 RunProductCommand bcp = thisBridge.bridgeAPI().runProductCommand();
784 bcp.setNodeAndMainParameter(nodeId, new VeluxProductPosition(new PercentType(Math.abs(relativePercent)))
785 .getAsRelativePosition((relativePercent >= 0)));
786 // background execution of moveRelative
787 getTaskExecutor().execute(() -> {
788 if (thisBridge.bridgeCommunicate(bcp)) {
789 logger.trace("moveRelative() command {}sucessfully sent to {}",
790 bcp.isCommunicationSuccessful() ? "" : "un", getThing().getUID());
799 * If necessary initialise the task executor and return it
801 * @return the task executor
803 private ExecutorService getTaskExecutor() {
804 ExecutorService taskExecutor = this.taskExecutor;
805 if (taskExecutor == null || taskExecutor.isShutdown()) {
806 taskExecutor = this.taskExecutor = Executors.newSingleThreadExecutor(getThreadFactory());
812 * If necessary initialise the thread factory and return it
814 * @return the thread factory
816 public NamedThreadFactory getThreadFactory() {
817 NamedThreadFactory threadFactory = this.threadFactory;
818 if (threadFactory == null) {
819 threadFactory = new NamedThreadFactory(getThing().getUID().getAsString());
821 return threadFactory;