2 * Copyright (c) 2010-2022 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.factory.VeluxHandlerFactory;
50 import org.openhab.binding.velux.internal.handler.utils.ExtendedBaseBridgeHandler;
51 import org.openhab.binding.velux.internal.handler.utils.Thing2VeluxActuator;
52 import org.openhab.binding.velux.internal.handler.utils.ThingProperty;
53 import org.openhab.binding.velux.internal.things.VeluxExistingProducts;
54 import org.openhab.binding.velux.internal.things.VeluxExistingScenes;
55 import org.openhab.binding.velux.internal.things.VeluxProduct;
56 import org.openhab.binding.velux.internal.things.VeluxProduct.ProductBridgeIndex;
57 import org.openhab.binding.velux.internal.things.VeluxProductPosition;
58 import org.openhab.binding.velux.internal.things.VeluxProductPosition.PositionType;
59 import org.openhab.binding.velux.internal.utils.Localization;
60 import org.openhab.core.common.AbstractUID;
61 import org.openhab.core.common.NamedThreadFactory;
62 import org.openhab.core.library.types.DecimalType;
63 import org.openhab.core.library.types.OnOffType;
64 import org.openhab.core.library.types.PercentType;
65 import org.openhab.core.thing.Bridge;
66 import org.openhab.core.thing.ChannelUID;
67 import org.openhab.core.thing.ThingStatus;
68 import org.openhab.core.thing.ThingStatusDetail;
69 import org.openhab.core.thing.ThingTypeUID;
70 import org.openhab.core.thing.binding.ThingHandlerService;
71 import org.openhab.core.types.Command;
72 import org.openhab.core.types.RefreshType;
73 import org.openhab.core.types.State;
74 import org.openhab.core.types.UnDefType;
75 import org.slf4j.Logger;
76 import org.slf4j.LoggerFactory;
79 * <B>Common interaction with the </B><I>Velux</I><B> bridge.</B>
81 * It implements the communication between <B>OpenHAB</B> and the <I>Velux</I> Bridge:
83 * <LI><B>OpenHAB</B> Event Bus → <I>Velux</I> <B>bridge</B>
85 * Sending commands and value updates.</LI>
88 * <LI><I>Velux</I> <B>bridge</B> → <B>OpenHAB</B>:
90 * Retrieving information by sending a Refresh command.</LI>
93 * Entry point for this class is the method
94 * {@link VeluxBridgeHandler#handleCommand handleCommand}.
96 * @author Guenther Schreiner - Initial contribution.
99 public class VeluxBridgeHandler extends ExtendedBaseBridgeHandler implements VeluxBridgeInstance, VeluxBridgeProvider {
102 * timeout to ensure that the binding shutdown will not block and stall the shutdown of OH itself
104 private static final int COMMUNICATION_TASK_MAX_WAIT_SECS = 10;
107 * a modifier string to avoid the (small) risk of other tasks (outside this binding) locking on the same ip address
108 * Strings.intern() object
111 private static final String LOCK_MODIFIER = "velux.ipaddr.";
113 private final Logger logger = LoggerFactory.getLogger(VeluxBridgeHandler.class);
118 * Scheduler for continuous refresh by scheduleWithFixedDelay.
120 private @Nullable ScheduledFuture<?> refreshSchedulerJob = null;
123 * Counter of refresh invocations by {@link refreshSchedulerJob}.
125 private int refreshCounter = 0;
128 * Dedicated task executor for the long-running bridge communication tasks.
130 * Note: there is no point in using multi threaded thread-pool here, since all the submitted (Runnable) tasks are
131 * anyway forced to go through the same serial pipeline, because they all call the same class level "synchronized"
132 * method to actually communicate with the KLF bridge via its one single TCP socket connection
134 private @Nullable ExecutorService communicationsJobExecutor = null;
135 private @Nullable NamedThreadFactory threadFactory = null;
137 private VeluxBridge myJsonBridge = new JsonVeluxBridge(this);
138 private VeluxBridge mySlipBridge = new SlipVeluxBridge(this);
139 private boolean disposing = false;
142 * **************************************
143 * ***** Default visibility Objects *****
146 VeluxBridge thisBridge = myJsonBridge;
147 public BridgeParameters bridgeParameters = new BridgeParameters();
148 Localization localization;
151 * Mapping from ChannelUID to class Thing2VeluxActuator, which return Velux device information, probably cached.
153 Map<ChannelUID, Thing2VeluxActuator> channel2VeluxActuator = new ConcurrentHashMap<>();
156 * Information retrieved by {@link VeluxBinding#VeluxBinding}.
158 private VeluxBridgeConfiguration veluxBridgeConfiguration = new VeluxBridgeConfiguration();
161 * ************************
162 * ***** Constructors *****
165 public VeluxBridgeHandler(final Bridge bridge, Localization localization) {
167 logger.trace("VeluxBridgeHandler(constructor with bridge={}, localization={}) called.", bridge, localization);
168 this.localization = localization;
169 logger.debug("Creating a VeluxBridgeHandler for thing '{}'.", getThing().getUID());
176 * Set of information retrieved from the bridge/gateway:
179 * <LI>{@link #actuators} - Already known actuators,</LI>
180 * <LI>{@link #scenes} - Already on the gateway defined scenes,</LI>
181 * <LI>{@link #gateway} - Current status of the gateway status,</LI>
182 * <LI>{@link #firmware} - Information about the gateway firmware revision,</LI>
183 * <LI>{@link #lanConfig} - Information about the gateway configuration,</LI>
184 * <LI>{@link #wlanConfig} - Information about the gateway configuration.</LI>
187 public class BridgeParameters {
188 /** Information retrieved by {@link VeluxBridgeActuators#getProducts} */
189 public VeluxBridgeActuators actuators = new VeluxBridgeActuators();
191 /** Information retrieved by {@link org.openhab.binding.velux.internal.bridge.VeluxBridgeScenes#getScenes} */
192 VeluxBridgeScenes scenes = new VeluxBridgeScenes();
194 /** Information retrieved by {@link VeluxBridgeDeviceStatus#retrieve} */
195 VeluxBridgeDeviceStatus.Channel gateway = new VeluxBridgeDeviceStatus().getChannel();
197 /** Information retrieved by {@link VeluxBridgeGetFirmware#retrieve} */
198 VeluxBridgeGetFirmware.Channel firmware = new VeluxBridgeGetFirmware().getChannel();
200 /** Information retrieved by {@link VeluxBridgeLANConfig#retrieve} */
201 VeluxBridgeLANConfig.Channel lanConfig = new VeluxBridgeLANConfig().getChannel();
203 /** Information retrieved by {@link VeluxBridgeWLANConfig#retrieve} */
204 VeluxBridgeWLANConfig.Channel wlanConfig = new VeluxBridgeWLANConfig().getChannel();
210 * Provide the ThingType for a given Channel.
212 * Separated into this private method to deal with the deprecated method.
215 * @param channelUID for type {@link ChannelUID}.
216 * @return thingTypeUID of type {@link ThingTypeUID}.
218 public ThingTypeUID thingTypeUIDOf(ChannelUID channelUID) {
219 String[] segments = channelUID.getAsString().split(AbstractUID.SEPARATOR);
220 if (segments.length > 1) {
221 return new ThingTypeUID(segments[0], segments[1]);
223 logger.warn("thingTypeUIDOf({}) failed.", channelUID);
224 return new ThingTypeUID(VeluxBindingConstants.BINDING_ID, VeluxBindingConstants.UNKNOWN_THING_TYPE_ID);
227 // Objects and Methods for interface VeluxBridgeInstance
230 * Information retrieved by ...
233 public VeluxBridgeConfiguration veluxBridgeConfiguration() {
234 return veluxBridgeConfiguration;
238 * Information retrieved by {@link VeluxBridgeActuators#getProducts}
241 public VeluxExistingProducts existingProducts() {
242 return bridgeParameters.actuators.getChannel().existingProducts;
246 * Information retrieved by {@link VeluxBridgeScenes#getScenes}
249 public VeluxExistingScenes existingScenes() {
250 return bridgeParameters.scenes.getChannel().existingScenes;
253 // Objects and Methods for interface VeluxBridgeProvider *****
256 public boolean bridgeCommunicate(BridgeCommunicationProtocol communication) {
257 logger.warn("bridgeCommunicate() called. Should never be called (as implemented by protocol-specific layers).");
262 public @Nullable BridgeAPI bridgeAPI() {
263 logger.warn("bridgeAPI() called. Should never be called (as implemented by protocol-specific layers).");
267 // Provisioning/Deprovisioning methods *****
270 public void initialize() {
271 // set the thing status to UNKNOWN temporarily and let the background task decide the real status
272 updateStatus(ThingStatus.UNKNOWN);
274 // take care of unusual situations...
275 if (scheduler.isShutdown()) {
276 logger.warn("initialize(): scheduler is shutdown, aborting initialization.");
280 logger.trace("initialize(): initialize bridge configuration parameters.");
281 veluxBridgeConfiguration = new VeluxBinding(getConfigAs(VeluxBridgeConfiguration.class)).checked();
283 scheduler.execute(() -> {
285 initializeSchedulerJob();
290 * Various initialisation actions to be executed on a background thread
292 private void initializeSchedulerJob() {
294 * synchronize disposeSchedulerJob() and initializeSchedulerJob() based an IP address Strings.intern() object to
295 * prevent overlap of initialization and disposal communications towards the same physical bridge
297 synchronized (LOCK_MODIFIER.concat(veluxBridgeConfiguration.ipAddress).intern()) {
298 logger.trace("initializeSchedulerJob(): adopt new bridge configuration parameters.");
299 bridgeParamsUpdated();
301 long mSecs = veluxBridgeConfiguration.refreshMSecs;
302 logger.trace("initializeSchedulerJob(): scheduling refresh at {} milliseconds.", mSecs);
303 refreshSchedulerJob = scheduler.scheduleWithFixedDelay(() -> {
304 refreshSchedulerJob();
305 }, mSecs, mSecs, TimeUnit.MILLISECONDS);
307 VeluxHandlerFactory.refreshBindingInfo();
309 if (logger.isDebugEnabled()) {
310 logger.debug("Velux Bridge '{}' is initialized (with {} scenes and {} actuators).", getThing().getUID(),
311 bridgeParameters.scenes.getChannel().existingScenes.getNoMembers(),
312 bridgeParameters.actuators.getChannel().existingProducts.getNoMembers());
318 public void dispose() {
319 scheduler.submit(() -> {
321 disposeSchedulerJob();
326 * Various disposal actions to be executed on a background thread
328 private void disposeSchedulerJob() {
330 * synchronize disposeSchedulerJob() and initializeSchedulerJob() based an IP address Strings.intern() object to
331 * prevent overlap of initialization and disposal communications towards the same physical bridge
333 synchronized (LOCK_MODIFIER.concat(veluxBridgeConfiguration.ipAddress).intern()) {
335 * cancel the regular refresh polling job
337 ScheduledFuture<?> refreshSchedulerJob = this.refreshSchedulerJob;
338 if (refreshSchedulerJob != null) {
339 logger.trace("disposeSchedulerJob(): cancel the refresh polling job.");
340 refreshSchedulerJob.cancel(false);
343 ExecutorService commsJobExecutor = this.communicationsJobExecutor;
344 if (commsJobExecutor != null) {
345 this.communicationsJobExecutor = null;
346 logger.trace("disposeSchedulerJob(): cancel any other scheduled jobs.");
348 * remove un-started communication tasks from the execution queue; and stop accepting more tasks
350 commsJobExecutor.shutdownNow();
352 * if the last bridge communication was OK, wait for already started task(s) to complete (so the bridge
353 * won't lock up); but to prevent stalling the OH shutdown process, time out after
354 * MAX_COMMUNICATION_TASK_WAIT_TIME_SECS
356 if (thisBridge.lastCommunicationOk()) {
358 if (!commsJobExecutor.awaitTermination(COMMUNICATION_TASK_MAX_WAIT_SECS, TimeUnit.SECONDS)) {
359 logger.warn("disposeSchedulerJob(): unexpected awaitTermination() timeout.");
361 } catch (InterruptedException e) {
362 logger.warn("disposeSchedulerJob(): unexpected exception awaitTermination() '{}'.",
369 * if the last bridge communication was OK, deactivate HSM to prevent queueing more HSM events
371 if (thisBridge.lastCommunicationOk()
372 && (new VeluxBridgeSetHouseStatusMonitor().modifyHSM(thisBridge, false))) {
373 logger.trace("disposeSchedulerJob(): HSM deactivated.");
377 * finally clean up everything else
379 logger.trace("disposeSchedulerJob(): shut down JSON connection interface.");
380 myJsonBridge.shutdown();
381 logger.trace("disposeSchedulerJob(): shut down SLIP connection interface.");
382 mySlipBridge.shutdown();
383 VeluxHandlerFactory.refreshBindingInfo();
384 logger.debug("Velux Bridge '{}' is shut down.", getThing().getUID());
389 * NOTE: It takes care by calling {@link #handleCommand} with the REFRESH command, that every used channel is
393 public void channelLinked(ChannelUID channelUID) {
394 if (thing.getStatus() == ThingStatus.ONLINE) {
395 channel2VeluxActuator.put(channelUID, new Thing2VeluxActuator(this, channelUID));
396 logger.trace("channelLinked({}) refreshing channel value with help of handleCommand as Thing is online.",
397 channelUID.getAsString());
398 handleCommand(channelUID, RefreshType.REFRESH);
400 logger.trace("channelLinked({}) doing nothing as Thing is not online.", channelUID.getAsString());
405 public void channelUnlinked(ChannelUID channelUID) {
406 logger.trace("channelUnlinked({}) called.", channelUID.getAsString());
409 // Reconfiguration methods
411 private void bridgeParamsUpdated() {
412 logger.debug("bridgeParamsUpdated() called.");
414 // Determine the appropriate bridge communication channel
415 boolean validBridgeFound = false;
416 if (myJsonBridge.supportedProtocols.contains(veluxBridgeConfiguration.protocol)) {
417 logger.debug("bridgeParamsUpdated(): choosing JSON as communication method.");
418 thisBridge = myJsonBridge;
419 validBridgeFound = true;
421 if (mySlipBridge.supportedProtocols.contains(veluxBridgeConfiguration.protocol)) {
422 logger.debug("bridgeParamsUpdated(): choosing SLIP as communication method.");
423 thisBridge = mySlipBridge;
424 validBridgeFound = true;
426 if (!validBridgeFound) {
427 logger.debug("No valid protocol selected, aborting this {} binding.", VeluxBindingConstants.BINDING_ID);
428 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
429 "@text/runtime.bridge-offline-no-valid-bridgeProtocol-selected");
430 logger.trace("bridgeParamsUpdated() done.");
434 logger.trace("bridgeParamsUpdated(): Trying to authenticate towards bridge.");
436 if (!thisBridge.bridgeLogin()) {
437 logger.warn("{} bridge login sequence failed; expecting bridge is OFFLINE.",
438 VeluxBindingConstants.BINDING_ID);
439 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
440 "@text/runtime.bridge-offline-login-sequence-failed");
441 logger.trace("bridgeParamsUpdated() done.");
445 logger.trace("bridgeParamsUpdated(): Querying bridge state.");
446 bridgeParameters.gateway = new VeluxBridgeDeviceStatus().retrieve(thisBridge);
448 logger.trace("bridgeParamsUpdated(): Fetching existing scenes.");
449 bridgeParameters.scenes.getScenes(thisBridge);
450 logger.debug("Found Velux scenes:\n\t{}",
451 bridgeParameters.scenes.getChannel().existingScenes.toString(false, "\n\t"));
452 logger.trace("bridgeParamsUpdated(): Fetching existing actuators/products.");
453 bridgeParameters.actuators.getProducts(thisBridge);
454 logger.debug("Found Velux actuators:\n\t{}",
455 bridgeParameters.actuators.getChannel().existingProducts.toString(false, "\n\t"));
457 if (thisBridge.bridgeAPI().setHouseStatusMonitor() != null) {
458 logger.trace("bridgeParamsUpdated(): Activating HouseStatusMonitor.");
459 if (new VeluxBridgeSetHouseStatusMonitor().modifyHSM(thisBridge, true)) {
460 logger.trace("bridgeParamsUpdated(): HSM activated.");
462 logger.warn("Activation of House-Status-Monitoring failed (might lead to a lack of status updates).");
466 veluxBridgeConfiguration.hasChanged = false;
467 logger.debug("Velux veluxBridge is online, now.");
468 updateStatus(ThingStatus.ONLINE);
469 logger.trace("bridgeParamsUpdated() successfully finished.");
472 // Continuous synchronization methods
474 private synchronized void refreshSchedulerJob() {
475 logger.debug("refreshSchedulerJob() initiated by {} starting cycle {}.", Thread.currentThread(),
477 logger.trace("refreshSchedulerJob(): processing of possible HSM messages.");
479 // Background execution of bridge related I/O
480 submitCommunicationsJob(() -> {
481 getHouseStatusCommsJob();
485 "refreshSchedulerJob(): loop through all (child things and bridge) linked channels needing a refresh");
486 for (ChannelUID channelUID : BridgeChannels.getAllLinkedChannelUIDs(this)) {
487 if (VeluxItemType.isToBeRefreshedNow(refreshCounter, thingTypeUIDOf(channelUID), channelUID.getId())) {
488 logger.trace("refreshSchedulerJob(): refreshing channel {}.", channelUID);
489 handleCommand(channelUID, RefreshType.REFRESH);
493 logger.trace("refreshSchedulerJob(): loop through properties needing a refresh");
494 for (VeluxItemType veluxItem : VeluxItemType.getPropertyEntriesByThing(getThing().getThingTypeUID())) {
495 if (VeluxItemType.isToBeRefreshedNow(refreshCounter, getThing().getThingTypeUID(),
496 veluxItem.getIdentifier())) {
497 logger.trace("refreshSchedulerJob(): refreshing property {}.", veluxItem.getIdentifier());
498 handleCommand(new ChannelUID(getThing().getUID(), veluxItem.getIdentifier()), RefreshType.REFRESH);
501 logger.debug("refreshSchedulerJob() initiated by {} finished cycle {}.", Thread.currentThread(),
506 private void getHouseStatusCommsJob() {
507 logger.trace("getHouseStatusCommsJob() initiated by {} will process HouseStatus.", Thread.currentThread());
508 if (new VeluxBridgeGetHouseStatus().evaluateState(thisBridge)) {
509 logger.trace("getHouseStatusCommsJob(): => GetHouseStatus() => updates received => synchronizing");
510 syncChannelsWithProducts();
512 logger.trace("getHouseStatusCommsJob(): => GetHouseStatus() => no updates");
514 logger.trace("getHouseStatusCommsJob() initiated by {} has finished.", Thread.currentThread());
518 * In case of recognized changes in the real world, the method will
519 * update the corresponding states via openHAB event bus.
521 private void syncChannelsWithProducts() {
522 if (!bridgeParameters.actuators.getChannel().existingProducts.isDirty()) {
523 logger.trace("syncChannelsWithProducts(): no existing products with changed parameters.");
526 logger.trace("syncChannelsWithProducts(): there are some existing products with changed parameters.");
527 for (VeluxProduct product : bridgeParameters.actuators.getChannel().existingProducts.valuesOfModified()) {
528 logger.trace("syncChannelsWithProducts(): actuator {} has changed values.", product.getProductName());
529 ProductBridgeIndex productPbi = product.getBridgeProductIndex();
530 logger.trace("syncChannelsWithProducts(): bridge index is {}.", productPbi);
531 for (ChannelUID channelUID : BridgeChannels.getAllLinkedChannelUIDs(this)) {
532 if (!channel2VeluxActuator.containsKey(channelUID)) {
533 logger.trace("syncChannelsWithProducts(): channel {} not found.", channelUID);
536 Thing2VeluxActuator actuator = channel2VeluxActuator.get(channelUID);
537 if (actuator == null || !actuator.isKnown()) {
538 logger.trace("syncChannelsWithProducts(): channel {} not registered on bridge.", channelUID);
541 ProductBridgeIndex channelPbi = actuator.getProductBridgeIndex();
542 if (!channelPbi.equals(productPbi)) {
546 VeluxProductPosition position;
547 if (channelUID.getId().equals(VeluxBindingConstants.CHANNEL_VANE_POSITION)) {
549 position = new VeluxProductPosition(product.getVanePosition());
551 // Handle value inversion
552 isInverted = actuator.isInverted();
553 logger.trace("syncChannelsWithProducts(): isInverted is {}.", isInverted);
554 position = new VeluxProductPosition(product.getDisplayPosition());
556 if (position.isValid()) {
557 PercentType positionAsPercent = position.getPositionAsPercentType(isInverted);
558 logger.debug("syncChannelsWithProducts(): updating channel {} to position {}%.", channelUID,
560 updateState(channelUID, positionAsPercent);
563 logger.trace("syncChannelsWithProducts(): updating channel {} to 'UNDEFINED'.", channelUID);
564 updateState(channelUID, UnDefType.UNDEF);
568 logger.trace("syncChannelsWithProducts(): resetting dirty flag.");
569 bridgeParameters.actuators.getChannel().existingProducts.resetDirtyFlag();
570 logger.trace("syncChannelsWithProducts() done.");
573 // Processing of openHAB events
576 public void handleCommand(ChannelUID channelUID, Command command) {
577 logger.trace("handleCommand({}): command {} on channel {} will be scheduled.", Thread.currentThread(), command,
578 channelUID.getAsString());
579 logger.debug("handleCommand({},{}) called.", channelUID.getAsString(), command);
581 // Background execution of bridge related I/O
582 submitCommunicationsJob(() -> {
583 handleCommandCommsJob(channelUID, command);
585 logger.trace("handleCommand({}) done.", Thread.currentThread());
589 * Normally called by {@link #handleCommand} to handle a command for a given channel with possibly long execution
592 * <B>NOTE:</B> This method is to be called as separated thread to ensure proper openHAB framework in parallel.
595 * @param channelUID the {@link ChannelUID} of the channel to which the command was sent,
596 * @param command the {@link Command}.
598 private synchronized void handleCommandCommsJob(ChannelUID channelUID, Command command) {
599 logger.trace("handleCommandCommsJob({}): command {} on channel {}.", Thread.currentThread(), command,
600 channelUID.getAsString());
601 logger.debug("handleCommandCommsJob({},{}) called.", channelUID.getAsString(), command);
604 * ===========================================================
608 if (veluxBridgeConfiguration.isProtocolTraceEnabled) {
609 Threads.findDeadlocked();
612 String channelId = channelUID.getId();
613 State newState = null;
614 String itemName = channelUID.getAsString();
615 VeluxItemType itemType = VeluxItemType.getByThingAndChannel(thingTypeUIDOf(channelUID), channelUID.getId());
617 if (itemType == VeluxItemType.UNKNOWN) {
618 logger.warn("{} Cannot determine type of Channel {}, ignoring command {}.",
619 VeluxBindingConstants.LOGGING_CONTACT, channelUID, command);
620 logger.trace("handleCommandCommsJob() aborting.");
625 if (!channel2VeluxActuator.containsKey(channelUID)) {
626 channel2VeluxActuator.put(channelUID, new Thing2VeluxActuator(this, channelUID));
629 if (veluxBridgeConfiguration.hasChanged) {
630 logger.trace("handleCommandCommsJob(): work on updated bridge configuration parameters.");
631 bridgeParamsUpdated();
634 syncChannelsWithProducts();
636 if (command instanceof RefreshType) {
638 * ===========================================================
641 logger.trace("handleCommandCommsJob(): work on refresh.");
642 if (!itemType.isReadable()) {
643 logger.debug("handleCommandCommsJob(): received a Refresh command for a non-readable item.");
645 logger.trace("handleCommandCommsJob(): refreshing item {} (type {}).", itemName, itemType);
646 try { // expecting an IllegalArgumentException for unknown Velux device
650 newState = ChannelBridgeStatus.handleRefresh(channelUID, channelId, this);
652 case BRIDGE_DOWNTIME:
653 newState = new DecimalType(
654 thisBridge.lastCommunication() - thisBridge.lastSuccessfulCommunication());
656 case BRIDGE_FIRMWARE:
657 newState = ChannelBridgeFirmware.handleRefresh(channelUID, channelId, this);
660 // delete legacy property name entry (if any) and fall through
661 ThingProperty.setValue(this, VeluxBridgeConfiguration.BRIDGE_IPADDRESS, null);
662 case BRIDGE_SUBNETMASK:
663 case BRIDGE_DEFAULTGW:
665 newState = ChannelBridgeLANconfig.handleRefresh(channelUID, channelId, this);
667 case BRIDGE_WLANSSID:
668 case BRIDGE_WLANPASSWORD:
669 newState = ChannelBridgeWLANconfig.handleRefresh(channelUID, channelId, this);
672 newState = ChannelBridgeScenes.handleRefresh(channelUID, channelId, this);
674 case BRIDGE_PRODUCTS:
675 newState = ChannelBridgeProducts.handleRefresh(channelUID, channelId, this);
678 newState = ChannelBridgeCheck.handleRefresh(channelUID, channelId, this);
681 case ACTUATOR_POSITION:
683 case ROLLERSHUTTER_POSITION:
684 case WINDOW_POSITION:
685 case ROLLERSHUTTER_VANE_POSITION:
686 newState = ChannelActuatorPosition.handleRefresh(channelUID, channelId, this);
688 case ACTUATOR_LIMIT_MINIMUM:
689 case ROLLERSHUTTER_LIMIT_MINIMUM:
690 case WINDOW_LIMIT_MINIMUM:
691 // note: the empty string ("") below is intentional
692 newState = ChannelActuatorLimitation.handleRefresh(channelUID, "", this);
694 case ACTUATOR_LIMIT_MAXIMUM:
695 case ROLLERSHUTTER_LIMIT_MAXIMUM:
696 case WINDOW_LIMIT_MAXIMUM:
697 newState = ChannelActuatorLimitation.handleRefresh(channelUID, channelId, this);
700 // VirtualShutter channels
701 case VSHUTTER_POSITION:
702 newState = ChannelVShutterPosition.handleRefresh(channelUID, channelId, this);
706 logger.warn("{} Cannot handle REFRESH on channel {} as it is of type {}.",
707 VeluxBindingConstants.LOGGING_CONTACT, itemName, channelId);
709 } catch (IllegalArgumentException e) {
710 logger.warn("Cannot handle REFRESH on channel {} as it isn't (yet) known to the bridge.", itemName);
712 if (newState != null) {
713 if (itemType.isChannel()) {
714 logger.debug("handleCommandCommsJob(): updating channel {} to {}.", channelUID, newState);
715 updateState(channelUID, newState);
716 } else if (itemType.isProperty()) {
717 // if property value is 'unknown', null it completely
718 String val = newState.toString();
719 if (VeluxBindingConstants.UNKNOWN.equals(val)) {
722 logger.debug("handleCommandCommsJob(): updating property {} to {}.", channelUID, val);
723 ThingProperty.setValue(this, itemType.getIdentifier(), val);
726 logger.warn("handleCommandCommsJob({},{}): updating of item {} (type {}) failed.",
727 channelUID.getAsString(), command, itemName, itemType);
732 * ===========================================================
735 logger.trace("handleCommandCommsJob(): working on item {} (type {}) with COMMAND {}.", itemName, itemType,
737 Command newValue = null;
738 try { // expecting an IllegalArgumentException for unknown Velux device
742 if (command == OnOffType.ON) {
743 logger.trace("handleCommandCommsJob(): about to reload informations from veluxBridge.");
744 bridgeParamsUpdated();
746 logger.trace("handleCommandCommsJob(): ignoring OFF command.");
749 case BRIDGE_DO_DETECTION:
750 ChannelBridgeDoDetection.handleCommand(channelUID, channelId, command, this);
755 ChannelSceneAction.handleCommand(channelUID, channelId, command, this);
759 * NOTA BENE: Setting of a scene silent mode is no longer supported via the KLF API (i.e. the
760 * GW_SET_NODE_VELOCITY_REQ/CFM command set is no longer supported in the API), so the binding can
761 * no longer explicitly support a Channel with such a function. Therefore the silent mode Channel
762 * type was removed from the binding implementation.
764 * By contrast scene actions can still be called with a silent mode argument, so a silent mode
765 * Configuration Parameter has been introduced as a means for the user to set this argument.
767 * Strictly speaking the following case statement will now never be called, so in theory it,
768 * AND ALL THE CLASSES BEHIND, could be deleted from the binding CODE BASE. But out of prudence
769 * it is retained anyway 'just in case'.
771 case SCENE_SILENTMODE:
772 ChannelSceneSilentmode.handleCommand(channelUID, channelId, command, this);
776 case ACTUATOR_POSITION:
778 case ROLLERSHUTTER_POSITION:
779 case WINDOW_POSITION:
780 case ROLLERSHUTTER_VANE_POSITION:
781 newValue = ChannelActuatorPosition.handleCommand(channelUID, channelId, command, this);
783 case ACTUATOR_LIMIT_MINIMUM:
784 case ROLLERSHUTTER_LIMIT_MINIMUM:
785 case WINDOW_LIMIT_MINIMUM:
786 ChannelActuatorLimitation.handleCommand(channelUID, channelId, command, this);
788 case ACTUATOR_LIMIT_MAXIMUM:
789 case ROLLERSHUTTER_LIMIT_MAXIMUM:
790 case WINDOW_LIMIT_MAXIMUM:
791 ChannelActuatorLimitation.handleCommand(channelUID, channelId, command, this);
794 // VirtualShutter channels
795 case VSHUTTER_POSITION:
796 newValue = ChannelVShutterPosition.handleCommand(channelUID, channelId, command, this);
800 logger.warn("{} Cannot handle command {} on channel {} (type {}).",
801 VeluxBindingConstants.LOGGING_CONTACT, command, itemName, itemType);
803 } catch (IllegalArgumentException e) {
804 logger.warn("Cannot handle command on channel {} as it isn't (yet) known to the bridge.", itemName);
806 if (newValue != null) {
807 postCommand(channelUID, newValue);
810 ThingProperty.setValue(this, VeluxBindingConstants.PROPERTY_BRIDGE_TIMESTAMP_ATTEMPT,
811 new java.util.Date(thisBridge.lastCommunication()).toString());
812 ThingProperty.setValue(this, VeluxBindingConstants.PROPERTY_BRIDGE_TIMESTAMP_SUCCESS,
813 new java.util.Date(thisBridge.lastSuccessfulCommunication()).toString());
814 logger.trace("handleCommandCommsJob({}) done.", Thread.currentThread());
818 * Register the exported actions
821 public Collection<Class<? extends ThingHandlerService>> getServices() {
822 return Collections.singletonList(VeluxActions.class);
826 * Exported method (called by an OpenHAB Rules Action) to issue a reboot command to the hub.
828 * @return true if the command could be issued
830 public boolean runReboot() {
831 logger.trace("runReboot() called on {}", getThing().getUID());
832 RunReboot bcp = thisBridge.bridgeAPI().runReboot();
834 // background execution of reboot process
835 submitCommunicationsJob(() -> {
836 if (thisBridge.bridgeCommunicate(bcp)) {
837 logger.info("Reboot command {}sucessfully sent to {}", bcp.isCommunicationSuccessful() ? "" : "un",
838 getThing().getUID());
847 * Exported method (called by an OpenHAB Rules Action) to move an actuator relative to its current position
849 * @param nodeId the node to be moved
850 * @param relativePercent relative position change to the current position (-100% <= relativePercent <= +100%)
851 * @return true if the command could be issued
853 public boolean moveRelative(int nodeId, int relativePercent) {
854 logger.trace("moveRelative() called on {}", getThing().getUID());
855 RunProductCommand bcp = thisBridge.bridgeAPI().runProductCommand();
857 // background execution of moveRelative
858 submitCommunicationsJob(() -> {
860 bcp.setNodeIdAndParameters(nodeId,
861 new VeluxProductPosition(new PercentType(Math.abs(relativePercent))).overridePositionType(
862 relativePercent > 0 ? PositionType.OFFSET_POSITIVE : PositionType.OFFSET_NEGATIVE),
864 if (thisBridge.bridgeCommunicate(bcp)) {
865 logger.trace("moveRelative() command {}sucessfully sent to {}",
866 bcp.isCommunicationSuccessful() ? "" : "un", getThing().getUID());
876 * If necessary initialise the communications job executor. Then check if the executor is shut down. And if it is
877 * not shut down, then submit the given communications job for execution.
879 private void submitCommunicationsJob(Runnable communicationsJob) {
880 ExecutorService commsJobExecutor = this.communicationsJobExecutor;
881 if (commsJobExecutor == null) {
882 commsJobExecutor = this.communicationsJobExecutor = Executors.newSingleThreadExecutor(getThreadFactory());
884 if (!commsJobExecutor.isShutdown()) {
885 commsJobExecutor.execute(communicationsJob);
890 * If necessary initialise the thread factory and return it
892 * @return the thread factory
894 public NamedThreadFactory getThreadFactory() {
895 NamedThreadFactory threadFactory = this.threadFactory;
896 if (threadFactory == null) {
897 threadFactory = new NamedThreadFactory(getThing().getUID().getAsString());
899 return threadFactory;
903 * Indicates if the bridge thing is being disposed.
905 * @return true if the bridge thing is being disposed.
907 public boolean isDisposing() {