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