]> git.basschouten.com Git - openhab-addons.git/blob
bf9a9ee5619fc624585b505aba3b56d6e67da80d
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 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 static org.openhab.binding.velux.internal.VeluxBindingConstants.*;
16
17 import org.eclipse.jdt.annotation.NonNullByDefault;
18 import org.eclipse.jdt.annotation.Nullable;
19 import org.openhab.binding.velux.internal.bridge.common.GetProduct;
20 import org.openhab.binding.velux.internal.bridge.common.RunProductCommand;
21 import org.openhab.binding.velux.internal.bridge.slip.FunctionalParameters;
22 import org.openhab.binding.velux.internal.bridge.slip.SCrunProductCommand;
23 import org.openhab.binding.velux.internal.handler.utils.Thing2VeluxActuator;
24 import org.openhab.binding.velux.internal.things.StatusReply;
25 import org.openhab.binding.velux.internal.things.VeluxExistingProducts;
26 import org.openhab.binding.velux.internal.things.VeluxProduct;
27 import org.openhab.binding.velux.internal.things.VeluxProduct.ProductBridgeIndex;
28 import org.openhab.binding.velux.internal.things.VeluxProduct.ProductState;
29 import org.openhab.binding.velux.internal.things.VeluxProductPosition;
30 import org.openhab.core.library.types.OnOffType;
31 import org.openhab.core.library.types.PercentType;
32 import org.openhab.core.library.types.StopMoveType;
33 import org.openhab.core.library.types.UpDownType;
34 import org.openhab.core.thing.ChannelUID;
35 import org.openhab.core.types.Command;
36 import org.openhab.core.types.State;
37 import org.openhab.core.types.UnDefType;
38 import org.slf4j.Logger;
39 import org.slf4j.LoggerFactory;
40
41 /**
42  * <B>Channel-specific retrieval and modification.</B>
43  * <P>
44  * This class implements the Channel <B>position</B> of the Thing <B>actuator</B>:
45  * <UL>
46  * <LI><I>Velux</I> <B>bridge</B> &rarr; <B>OpenHAB</B>:
47  * <P>
48  * Information retrieval by method {@link #handleRefresh}.</LI>
49  * </UL>
50  * <UL>
51  * <LI><B>OpenHAB</B> Event Bus &rarr; <I>Velux</I> <B>bridge</B>
52  * <P>
53  * Sending commands and value updates by method {@link #handleCommand}.</LI>
54  * </UL>
55  *
56  * @author Guenther Schreiner - Initial contribution.
57  * @author Andrew Fiddian-Green - Refactoring and use alternate API set for Vane Position.
58  */
59 @NonNullByDefault
60 final class ChannelActuatorPosition extends ChannelHandlerTemplate {
61     private static final Logger LOGGER = LoggerFactory.getLogger(ChannelActuatorPosition.class);
62
63     // Constructors
64
65     /**
66      * Suppress default constructor for non-instantiability.
67      */
68     private ChannelActuatorPosition() {
69         throw new AssertionError();
70     }
71
72     // Public methods
73
74     /**
75      * Communication method to retrieve information to update the channel value.
76      *
77      * @param channelUID The item passed as type {@link ChannelUID} for which a refresh is intended.
78      * @param channelId The same item passed as type {@link String} for which a refresh is intended.
79      * @param thisBridgeHandler The Velux bridge handler with a specific communication protocol which provides
80      *            information for this channel.
81      * @return newState The value retrieved for the passed channel, or <I>null</I> in case if there is no (new) value.
82      */
83     static @Nullable State handleRefresh(ChannelUID channelUID, String channelId,
84             VeluxBridgeHandler thisBridgeHandler) {
85         LOGGER.debug("handleRefresh({},{},{}) called.", channelUID, channelId, thisBridgeHandler);
86         State newState = null;
87         do { // just for common exit
88             if (thisBridgeHandler.bridgeParameters.actuators.autoRefresh(thisBridgeHandler.thisBridge)) {
89                 LOGGER.trace("handleRefresh(): there are some existing products.");
90             }
91
92             Thing2VeluxActuator veluxActuator = thisBridgeHandler.channel2VeluxActuator.get(channelUID);
93             if (veluxActuator == null || !veluxActuator.isKnown()) {
94                 LOGGER.warn("handleRefresh(): unknown actuator.");
95                 break;
96             }
97
98             final VeluxExistingProducts existingProducts = thisBridgeHandler.existingProducts();
99
100             GetProduct bcp = null;
101             switch (channelId) {
102                 case CHANNEL_ACTUATOR_POSITION:
103                 case CHANNEL_ACTUATOR_STATE:
104                     // apparently Somfy products do not to support new API; so use older API instead
105                     if (existingProducts.get(veluxActuator.getProductBridgeIndex()).isSomfyProduct()) {
106                         bcp = thisBridgeHandler.thisBridge.bridgeAPI().getProduct();
107                         break;
108                     }
109                     // fall through
110                 case CHANNEL_VANE_POSITION:
111                     bcp = thisBridgeHandler.thisBridge.bridgeAPI().getProductStatus();
112                 default:
113                     // unknown channel, will exit
114             }
115
116             if (bcp == null) {
117                 LOGGER.trace("handleRefresh(): aborting processing as handler is null.");
118                 break;
119             }
120
121             bcp.setProductId(veluxActuator.getProductBridgeIndex().toInt());
122             if ((!thisBridgeHandler.thisBridge.bridgeCommunicate(bcp)) || (!bcp.isCommunicationSuccessful())) {
123                 LOGGER.trace("handleRefresh(): bridge communication request failed.");
124                 break;
125             }
126
127             VeluxProduct newProduct = bcp.getProduct();
128             ProductBridgeIndex productBridgeIndex = newProduct.getBridgeProductIndex();
129             VeluxProduct existingProduct = existingProducts.get(productBridgeIndex);
130             ProductState productState = newProduct.getProductState();
131             switch (productState) {
132                 case DONE:
133                 case EXECUTING:
134                 case MANUAL:
135                 case UNKNOWN:
136                     if (!VeluxProduct.UNKNOWN.equals(existingProduct)) {
137                         switch (channelId) {
138                             case CHANNEL_VANE_POSITION:
139                             case CHANNEL_ACTUATOR_POSITION:
140                             case CHANNEL_ACTUATOR_STATE: {
141                                 if (existingProducts.update(newProduct)) {
142                                     existingProduct = existingProducts.get(productBridgeIndex);
143                                     int posValue = VeluxProductPosition.VPP_VELUX_UNKNOWN;
144                                     switch (channelId) {
145                                         case CHANNEL_VANE_POSITION:
146                                             posValue = existingProduct.getVaneDisplayPosition();
147                                             break;
148                                         case CHANNEL_ACTUATOR_POSITION:
149                                         case CHANNEL_ACTUATOR_STATE:
150                                             posValue = existingProduct.getDisplayPosition();
151                                     }
152                                     VeluxProductPosition position = new VeluxProductPosition(posValue);
153                                     if (position.isValid()) {
154                                         switch (channelId) {
155                                             case CHANNEL_VANE_POSITION:
156                                                 newState = position.getPositionAsPercentType(false);
157                                                 break;
158                                             case CHANNEL_ACTUATOR_POSITION:
159                                                 newState = position
160                                                         .getPositionAsPercentType(veluxActuator.isInverted());
161                                                 break;
162                                             case CHANNEL_ACTUATOR_STATE:
163                                                 newState = OnOffType.from(
164                                                         position.getPositionAsPercentType(veluxActuator.isInverted())
165                                                                 .intValue() > 50);
166                                         }
167                                     }
168                                 }
169                             }
170                         }
171                     }
172                     break;
173                 case WAITING_FOR_POWER:
174                 case ERROR:
175                     StatusReply statusReply = productState == ProductState.WAITING_FOR_POWER
176                             ? StatusReply.NODE_WAITING_FOR_POWER
177                             : bcp.getStatusReply();
178                     if (statusReply.isError()) {
179                         String id = VeluxProduct.UNKNOWN.equals(existingProduct)
180                                 ? newProduct.getBridgeProductIndex().toString()
181                                 : existingProduct.getProductUniqueIndex();
182                         if (statusReply.isCriticalError()) {
183                             LOGGER.warn("Product Id:{} encountered an error with StatusReply:{}", id, statusReply);
184                         } else {
185                             LOGGER.info("Product Id:{} encountered an error with StatusReply:{}", id, statusReply);
186                         }
187                     }
188                 default:
189             }
190
191             if (newState == null) {
192                 newState = UnDefType.UNDEF;
193             }
194         } while (false); // common exit
195         LOGGER.trace("handleRefresh(): new state for channel id '{}' is '{}'.", channelId, newState);
196         return newState;
197     }
198
199     /**
200      * Communication method to update the real world according to the passed channel value (or command).
201      *
202      * @param channelUID The item passed as type {@link ChannelUID} for which to following command is addressed to.
203      * @param channelId The same item passed as type {@link String} for which a refresh is intended.
204      * @param command The command passed as type {@link Command} for the mentioned item.
205      * @param thisBridgeHandler The Velux bridge handler with a specific communication protocol which provides
206      *            information for this channel.
207      * @return newValue ...
208      */
209     static @Nullable Command handleCommand(ChannelUID channelUID, String channelId, Command command,
210             VeluxBridgeHandler thisBridgeHandler) {
211         LOGGER.debug("handleCommand({},{},{},{}) called.", channelUID, channelId, command, thisBridgeHandler);
212         Command newValue = null;
213         do { // just for common exit
214             if (thisBridgeHandler.bridgeParameters.actuators.autoRefresh(thisBridgeHandler.thisBridge)) {
215                 LOGGER.trace("handleCommand(): there are some existing products.");
216             }
217
218             Thing2VeluxActuator veluxActuator = thisBridgeHandler.channel2VeluxActuator.get(channelUID);
219             if (veluxActuator == null || !veluxActuator.isKnown()) {
220                 LOGGER.warn("handleRefresh(): unknown actuator.");
221                 break;
222             }
223
224             VeluxProductPosition mainParameter = null;
225             FunctionalParameters functionalParameters = null;
226             VeluxExistingProducts existingProducts = thisBridgeHandler.existingProducts();
227             ProductBridgeIndex productBridgeIndex = veluxActuator.getProductBridgeIndex();
228
229             switch (channelId) {
230                 case CHANNEL_VANE_POSITION:
231                     if (command instanceof PercentType) {
232                         VeluxProduct existingProductClone = existingProducts.get(productBridgeIndex).clone();
233                         existingProductClone.setVanePosition(
234                                 new VeluxProductPosition((PercentType) command).getPositionAsVeluxType());
235                         functionalParameters = existingProductClone.getFunctionalParameters();
236                     }
237                     break;
238
239                 case CHANNEL_ACTUATOR_POSITION:
240                     if (command instanceof UpDownType) {
241                         mainParameter = UpDownType.UP.equals(command) ^ veluxActuator.isInverted()
242                                 ? new VeluxProductPosition(PercentType.ZERO)
243                                 : new VeluxProductPosition(PercentType.HUNDRED);
244                     } else if (command instanceof StopMoveType) {
245                         mainParameter = StopMoveType.STOP.equals(command) ? new VeluxProductPosition() : mainParameter;
246                     } else if (command instanceof PercentType) {
247                         PercentType ptCommand = (PercentType) command;
248                         if (veluxActuator.isInverted()) {
249                             ptCommand = new PercentType(PercentType.HUNDRED.intValue() - ptCommand.intValue());
250                         }
251                         mainParameter = new VeluxProductPosition(ptCommand);
252                     }
253                     break;
254
255                 case CHANNEL_ACTUATOR_STATE:
256                     if (command instanceof OnOffType) {
257                         mainParameter = OnOffType.OFF.equals(command) ^ veluxActuator.isInverted()
258                                 ? new VeluxProductPosition(PercentType.ZERO)
259                                 : new VeluxProductPosition(PercentType.HUNDRED);
260                     }
261                     break;
262
263                 default:
264                     // unknown channel => do nothing..
265             }
266
267             if ((mainParameter != null) || (functionalParameters != null)) {
268                 LOGGER.debug("handleCommand(): sending command '{}' for channel id '{}'.", command, channelId);
269                 RunProductCommand bcp = thisBridgeHandler.thisBridge.bridgeAPI().runProductCommand();
270                 boolean success = false;
271                 if (bcp instanceof SCrunProductCommand) {
272                     synchronized (bcp) {
273                         if (bcp.setNodeIdAndParameters(productBridgeIndex.toInt(), mainParameter, functionalParameters)
274                                 && thisBridgeHandler.thisBridge.bridgeCommunicate(bcp)
275                                 && bcp.isCommunicationSuccessful()) {
276                             success = true;
277                             if (thisBridgeHandler.bridgeParameters.actuators
278                                     .autoRefresh(thisBridgeHandler.thisBridge)) {
279                                 LOGGER.trace("handleCommand(): actuator position will be updated via polling.");
280                             }
281                             if (existingProducts.update(((SCrunProductCommand) bcp).getProduct())) {
282                                 LOGGER.trace("handleCommand(): actuator position immediate update requested.");
283                             }
284                         }
285                     }
286                 }
287                 LOGGER.debug("handleCommand(): sendCommand() finished {}.",
288                         (success ? "successfully" : "with failure"));
289             } else {
290                 LOGGER.info("handleCommand(): ignoring command '{}' for channel id '{}'.", command, channelId);
291             }
292         } while (false); // common exit
293         return newValue;
294     }
295 }