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.bridge.slip;
15 import java.util.Random;
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;
31 * Protocol specific bridge communication supported by the Velux bridge:
32 * <B>Retrieve Product Status</B>
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.
38 * This API set is the one used (for example) by Home Assistant.
40 * @author Andrew Fiddian-Green - Initial contribution.
43 public class SCgetProductStatus extends GetProduct implements SlipBridgeCommunicationProtocol {
44 private final Logger logger = LoggerFactory.getLogger(SCgetProductStatus.class);
46 private static final String DESCRIPTION = "Retrieve Product Status";
47 private static final Command COMMAND = Command.GW_STATUS_REQUEST_REQ;
50 * RunStatus and StatusReply parameter values (from KLF200 API specification)
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;
59 * ===========================================================
60 * Message Content Parameters
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.
71 * ===========================================================
75 private byte[] requestData = new byte[0];
78 * ===========================================================
82 private boolean success = false;
83 private boolean finished = false;
85 private VeluxProduct product = VeluxProduct.UNKNOWN;
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);
95 * ===========================================================
96 * Methods required for interface {@link BridgeCommunicationProtocol}.
100 public String name() {
105 public CommandNumber getRequestCommand() {
108 logger.debug("getRequestCommand() returns {} ({}).", COMMAND.name(), COMMAND.getCommand());
109 return COMMAND.getCommand();
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();
128 public void setResponse(short responseCommand, byte[] thisResponseData, boolean isSequentialEnforced) {
129 KLF200Response.introLogging(logger, responseCommand, thisResponseData);
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)) {
140 int cfmSessionID = responseData.getTwoByteValue(0);
141 int cfmStatus = responseData.getOneByteValue(2);
144 logger.info("setResponse(): returned status: Error – Command rejected.");
148 logger.debug("setResponse(): returned status: OK - Command is accepted.");
149 if (!KLF200Response.check4matchingSessionID(logger, cfmSessionID, reqSessionID)) {
154 logger.warn("setResponse(): returned status={} (not defined).", cfmStatus);
160 case GW_STATUS_REQUEST_NTF:
161 if (!KLF200Response.isLengthValid(logger, responseCommand, thisResponseData, 59)) {
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);
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);
191 if (!KLF200Response.check4matchingNodeID(logger, reqNodeId, ntfNodeID)) {
195 int ntfCurrentPosition;
196 if ((ntfStatusCount > 0) && (ntfFirstParameterIndex == 0)) {
197 ntfCurrentPosition = ntfFirstParameter;
199 ntfCurrentPosition = VeluxProductPosition.VPP_VELUX_UNKNOWN;
203 switch (ntfRunStatus) {
204 case EXECUTION_ACTIVE:
205 ntfState = VeluxProduct.ProductState.EXECUTING.value;
207 case EXECUTION_COMPLETED:
208 ntfState = VeluxProduct.ProductState.DONE.value;
210 case EXECUTION_FAILED:
212 switch (ntfStatusReply) {
213 case UNKNOWN_STATUS_REPLY:
214 ntfState = VeluxProduct.ProductState.UNKNOWN.value;
216 case COMMAND_COMPLETED_OK:
217 ntfState = VeluxProduct.ProductState.DONE.value;
220 ntfState = VeluxProduct.ProductState.ERROR.value;
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);
230 if (!isSequentialEnforced) {
232 "setResponse(): skipping wait for more packets as sequential processing is not enforced.");
237 case GW_SESSION_FINISHED_NTF:
239 if (!KLF200Response.isLengthValid(logger, responseCommand, thisResponseData, 2)) {
242 int finishedNtfSessionID = responseData.getTwoByteValue(0);
243 if (!KLF200Response.check4matchingSessionID(logger, finishedNtfSessionID, reqSessionID)) {
246 logger.debug("setResponse(): finishedNtfSessionID={}.", finishedNtfSessionID);
251 KLF200Response.errorLogging(logger, responseCommand);
254 KLF200Response.outroLogging(logger, success, finished);
258 public boolean isCommunicationFinished() {
263 public boolean isCommunicationSuccessful() {
268 * ===========================================================
269 * Methods in addition to the interface {@link BridgeCommunicationProtocol}
270 * and the abstract class {@link GetProduct}
274 public void setProductId(int nodeId) {
275 logger.trace("setProductId({}) called.", nodeId);
280 public VeluxProduct getProduct() {
281 logger.trace("getProduct(): returning {}.", product);