]> git.basschouten.com Git - openhab-addons.git/blob
2fd8d4f015dd5ada2fd019006e342dce8fbeee0c
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2022 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.bridge.slip;
14
15 import java.util.Random;
16
17 import org.eclipse.jdt.annotation.NonNullByDefault;
18 import org.openhab.binding.velux.internal.bridge.common.GetProduct;
19 import org.openhab.binding.velux.internal.bridge.slip.utils.KLF200Response;
20 import org.openhab.binding.velux.internal.bridge.slip.utils.Packet;
21 import org.openhab.binding.velux.internal.things.VeluxKLFAPI.Command;
22 import org.openhab.binding.velux.internal.things.VeluxKLFAPI.CommandNumber;
23 import org.openhab.binding.velux.internal.things.VeluxProduct;
24 import org.openhab.binding.velux.internal.things.VeluxProduct.ProductBridgeIndex;
25 import org.openhab.binding.velux.internal.things.VeluxProductName;
26 import org.openhab.binding.velux.internal.things.VeluxProductPosition;
27 import org.slf4j.Logger;
28 import org.slf4j.LoggerFactory;
29
30 /**
31  * Protocol specific bridge communication supported by the Velux bridge:
32  * <B>Retrieve Product Status</B>
33  * <p>
34  * This implements an alternate API set (vs. the API set used by ScgetProduct) for retrieving a product's status. This
35  * alternate API set was added to the code base because, when using ScgetProduct, some products (e.g. Somfy) would
36  * produce buggy values in their Functional Parameters when reporting their Vane Position.
37  * <p>
38  * This API set is the one used (for example) by Home Assistant.
39  *
40  * @author Andrew Fiddian-Green - Initial contribution.
41  */
42 @NonNullByDefault
43 public class SCgetProductStatus extends GetProduct implements SlipBridgeCommunicationProtocol {
44     private final Logger logger = LoggerFactory.getLogger(SCgetProductStatus.class);
45
46     private static final String DESCRIPTION = "Retrieve Product Status";
47     private static final Command COMMAND = Command.GW_STATUS_REQUEST_REQ;
48
49     /*
50      * RunStatus and StatusReply parameter values (from KLF200 API specification)
51      */
52     private static final int EXECUTION_COMPLETED = 0;// Execution is completed with no errors.
53     private static final int EXECUTION_FAILED = 1; // Execution has failed. (Get specifics in the following error code)
54     private static final int EXECUTION_ACTIVE = 2;// Execution is still active
55     private static final int UNKNOWN_STATUS_REPLY = 0x00; // Used to indicate unknown reply.
56     private static final int COMMAND_COMPLETED_OK = 0x01;
57
58     /*
59      * ===========================================================
60      * Message Content Parameters
61      */
62
63     private int reqSessionID = 0; // The session id
64     private final int reqIndexArrayCount = 1; // One node will be addressed
65     private int reqNodeId = 1; // This is the node id
66     private final int reqStatusType = 1; // The current value
67     private final int reqFPI1 = 0xF0; // Functional Parameter Indicator 1 bit map (set to fetch { FP1 .. FP4 }
68     private final int reqFPI2 = 0; // Functional Parameter Indicator 2 bit map.
69
70     /*
71      * ===========================================================
72      * Message Objects
73      */
74
75     private byte[] requestData = new byte[0];
76
77     /*
78      * ===========================================================
79      * Result Objects
80      */
81
82     private boolean success = false;
83     private boolean finished = false;
84
85     private VeluxProduct product = VeluxProduct.UNKNOWN;
86
87     public SCgetProductStatus() {
88         logger.debug("SCgetProductStatus(Constructor) called.");
89         Random rand = new Random();
90         reqSessionID = rand.nextInt(0x0fff);
91         logger.debug("SCgetProductStatus(): starting session with the random number {}.", reqSessionID);
92     }
93
94     /*
95      * ===========================================================
96      * Methods required for interface {@link BridgeCommunicationProtocol}.
97      */
98
99     @Override
100     public String name() {
101         return DESCRIPTION;
102     }
103
104     @Override
105     public CommandNumber getRequestCommand() {
106         success = false;
107         finished = false;
108         logger.debug("getRequestCommand() returns {} ({}).", COMMAND.name(), COMMAND.getCommand());
109         return COMMAND.getCommand();
110     }
111
112     @Override
113     public byte[] getRequestDataAsArrayOfBytes() {
114         logger.trace("getRequestDataAsArrayOfBytes() returns data for retrieving node with id {}.", reqNodeId);
115         reqSessionID = (reqSessionID + 1) & 0xffff;
116         Packet request = new Packet(new byte[26]);
117         request.setTwoByteValue(0, reqSessionID);
118         request.setOneByteValue(2, reqIndexArrayCount);
119         request.setOneByteValue(3, reqNodeId);
120         request.setOneByteValue(23, reqStatusType);
121         request.setOneByteValue(24, reqFPI1);
122         request.setOneByteValue(25, reqFPI2);
123         requestData = request.toByteArray();
124         return requestData;
125     }
126
127     @Override
128     public void setResponse(short responseCommand, byte[] thisResponseData, boolean isSequentialEnforced) {
129         KLF200Response.introLogging(logger, responseCommand, thisResponseData);
130         success = false;
131         finished = false;
132         Packet responseData = new Packet(thisResponseData);
133         Command responseCmd = Command.get(responseCommand);
134         switch (responseCmd) {
135             case GW_STATUS_REQUEST_CFM:
136                 if (!KLF200Response.isLengthValid(logger, responseCommand, thisResponseData, 3)) {
137                     finished = true;
138                     break;
139                 }
140                 int cfmSessionID = responseData.getTwoByteValue(0);
141                 int cfmStatus = responseData.getOneByteValue(2);
142                 switch (cfmStatus) {
143                     case 0:
144                         logger.info("setResponse(): returned status: Error – Command rejected.");
145                         finished = true;
146                         break;
147                     case 1:
148                         logger.debug("setResponse(): returned status: OK - Command is accepted.");
149                         if (!KLF200Response.check4matchingSessionID(logger, cfmSessionID, reqSessionID)) {
150                             finished = true;
151                         }
152                         break;
153                     default:
154                         logger.warn("setResponse(): returned status={} (not defined).", cfmStatus);
155                         finished = true;
156                         break;
157                 }
158                 break;
159
160             case GW_STATUS_REQUEST_NTF:
161                 if (!KLF200Response.isLengthValid(logger, responseCommand, thisResponseData, 59)) {
162                     finished = true;
163                     break;
164                 }
165
166                 // Extracting information items
167                 int ntfSessionID = responseData.getTwoByteValue(0);
168                 int ntfStatusID = responseData.getOneByteValue(2);
169                 int ntfNodeID = responseData.getOneByteValue(3);
170                 int ntfRunStatus = responseData.getOneByteValue(4);
171                 int ntfStatusReply = responseData.getOneByteValue(5);
172                 int ntfStatusType = responseData.getOneByteValue(6);
173                 int ntfStatusCount = responseData.getOneByteValue(7);
174                 int ntfFirstParameterIndex = responseData.getOneByteValue(8);
175                 int ntfFirstParameter = responseData.getTwoByteValue(9);
176                 FunctionalParameters ntfFunctionalParameters = FunctionalParameters.readArrayIndexed(responseData, 11);
177
178                 if (logger.isTraceEnabled()) {
179                     logger.trace("setResponse(): ntfSessionID={}.", ntfSessionID);
180                     logger.trace("setResponse(): ntfStatusID={}.", ntfStatusID);
181                     logger.trace("setResponse(): ntfNodeID={}.", ntfNodeID);
182                     logger.trace("setResponse(): ntfRunStatus={}.", ntfRunStatus);
183                     logger.trace("setResponse(): ntfStatusReply={}.", ntfStatusReply);
184                     logger.trace("setResponse(): ntfStatusType={}.", ntfStatusType);
185                     logger.trace("setResponse(): ntfStatusCount={}.", ntfStatusCount);
186                     logger.trace("setResponse(): ntfFirstParameterIndex={}.", ntfFirstParameterIndex);
187                     logger.trace("setResponse(): ntfFirstParameter={}.", String.format("0x%04X", ntfFirstParameter));
188                     logger.trace("setResponse(): ntfFunctionalParameters={}.", ntfFunctionalParameters);
189                 }
190
191                 if (!KLF200Response.check4matchingNodeID(logger, reqNodeId, ntfNodeID)) {
192                     break;
193                 }
194
195                 int ntfCurrentPosition;
196                 if ((ntfStatusCount > 0) && (ntfFirstParameterIndex == 0)) {
197                     ntfCurrentPosition = ntfFirstParameter;
198                 } else {
199                     ntfCurrentPosition = VeluxProductPosition.VPP_VELUX_UNKNOWN;
200                 }
201
202                 int ntfState;
203                 switch (ntfRunStatus) {
204                     case EXECUTION_ACTIVE:
205                         ntfState = VeluxProduct.ProductState.EXECUTING.value;
206                         break;
207                     case EXECUTION_COMPLETED:
208                         ntfState = VeluxProduct.ProductState.DONE.value;
209                         break;
210                     case EXECUTION_FAILED:
211                     default:
212                         switch (ntfStatusReply) {
213                             case UNKNOWN_STATUS_REPLY:
214                                 ntfState = VeluxProduct.ProductState.UNKNOWN.value;
215                                 break;
216                             case COMMAND_COMPLETED_OK:
217                                 ntfState = VeluxProduct.ProductState.DONE.value;
218                                 break;
219                             default:
220                                 ntfState = VeluxProduct.ProductState.ERROR.value;
221                         }
222                         break;
223                 }
224
225                 // create notification product with the returned values
226                 product = new VeluxProduct(VeluxProductName.UNKNOWN, new ProductBridgeIndex(ntfNodeID), ntfState,
227                         ntfCurrentPosition, VeluxProductPosition.VPP_VELUX_IGNORE, ntfFunctionalParameters, COMMAND);
228
229                 success = true;
230                 if (!isSequentialEnforced) {
231                     logger.trace(
232                             "setResponse(): skipping wait for more packets as sequential processing is not enforced.");
233                     finished = true;
234                 }
235                 break;
236
237             case GW_SESSION_FINISHED_NTF:
238                 finished = true;
239                 if (!KLF200Response.isLengthValid(logger, responseCommand, thisResponseData, 2)) {
240                     break;
241                 }
242                 int finishedNtfSessionID = responseData.getTwoByteValue(0);
243                 if (!KLF200Response.check4matchingSessionID(logger, finishedNtfSessionID, reqSessionID)) {
244                     break;
245                 }
246                 logger.debug("setResponse(): finishedNtfSessionID={}.", finishedNtfSessionID);
247                 success = true;
248                 break;
249
250             default:
251                 KLF200Response.errorLogging(logger, responseCommand);
252                 finished = true;
253         }
254         KLF200Response.outroLogging(logger, success, finished);
255     }
256
257     @Override
258     public boolean isCommunicationFinished() {
259         return finished;
260     }
261
262     @Override
263     public boolean isCommunicationSuccessful() {
264         return success;
265     }
266
267     /*
268      * ===========================================================
269      * Methods in addition to the interface {@link BridgeCommunicationProtocol}
270      * and the abstract class {@link GetProduct}
271      */
272
273     @Override
274     public void setProductId(int nodeId) {
275         logger.trace("setProductId({}) called.", nodeId);
276         reqNodeId = nodeId;
277     }
278
279     @Override
280     public VeluxProduct getProduct() {
281         logger.trace("getProduct(): returning {}.", product);
282         return product;
283     }
284 }