2 * Copyright (c) 2010-2023 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.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;
32 * Protocol specific bridge communication supported by the Velux bridge:
33 * <B>Retrieve Product Status</B>
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.
39 * This API set is the one used (for example) by Home Assistant.
41 * @author Andrew Fiddian-Green - Initial contribution.
44 public class SCgetProductStatus extends GetProduct implements SlipBridgeCommunicationProtocol {
45 private final Logger logger = LoggerFactory.getLogger(SCgetProductStatus.class);
47 private static final String DESCRIPTION = "Retrieve Product Status";
48 private static final Command COMMAND = Command.GW_STATUS_REQUEST_REQ;
51 * RunStatus and StatusReply parameter values (from KLF200 API specification)
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;
60 * ===========================================================
61 * Message Content Parameters
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.
72 * ===========================================================
76 private byte[] requestData = new byte[0];
79 * ===========================================================
83 private boolean success = false;
84 private boolean finished = false;
85 private VeluxProduct product = VeluxProduct.UNKNOWN;
86 private StatusReply statusReply = StatusReply.COMMAND_COMPLETED_OK;
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);
96 * ===========================================================
97 * Methods required for interface {@link BridgeCommunicationProtocol}.
101 public String name() {
106 public CommandNumber getRequestCommand() {
109 logger.debug("getRequestCommand() returns {} ({}).", COMMAND.name(), COMMAND.getCommand());
110 return COMMAND.getCommand();
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();
129 public void setResponse(short responseCommand, byte[] thisResponseData, boolean isSequentialEnforced) {
130 KLF200Response.introLogging(logger, responseCommand, thisResponseData);
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)) {
141 int cfmSessionID = responseData.getTwoByteValue(0);
142 int cfmStatus = responseData.getOneByteValue(2);
145 logger.info("setResponse(): returned status: Error – Command rejected.");
149 logger.debug("setResponse(): returned status: OK - Command is accepted.");
150 if (!KLF200Response.check4matchingSessionID(logger, cfmSessionID, reqSessionID)) {
155 logger.warn("setResponse(): returned status={} (not defined).", cfmStatus);
161 case GW_STATUS_REQUEST_NTF:
162 if (!KLF200Response.isLengthValid(logger, responseCommand, thisResponseData, 59)) {
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);
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);
192 if (!KLF200Response.check4matchingNodeID(logger, reqNodeId, ntfNodeID)) {
196 int ntfCurrentPosition;
197 if ((ntfStatusCount > 0) && (ntfFirstParameterIndex == 0)) {
198 ntfCurrentPosition = ntfFirstParameter;
200 ntfCurrentPosition = VeluxProductPosition.VPP_VELUX_UNKNOWN;
204 switch (ntfRunStatus) {
205 case EXECUTION_ACTIVE:
206 ntfState = VeluxProduct.ProductState.EXECUTING.value;
208 case EXECUTION_COMPLETED:
209 ntfState = VeluxProduct.ProductState.DONE.value;
211 case EXECUTION_FAILED:
213 switch (ntfStatusReply) {
214 case UNKNOWN_STATUS_REPLY:
215 ntfState = VeluxProduct.ProductState.UNKNOWN.value;
217 case COMMAND_COMPLETED_OK:
218 ntfState = VeluxProduct.ProductState.DONE.value;
221 ntfState = VeluxProduct.ProductState.ERROR.value;
222 statusReply = StatusReply.fromCode(ntfStatusReply);
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);
232 if (!isSequentialEnforced) {
234 "setResponse(): skipping wait for more packets as sequential processing is not enforced.");
239 case GW_SESSION_FINISHED_NTF:
241 if (!KLF200Response.isLengthValid(logger, responseCommand, thisResponseData, 2)) {
244 int finishedNtfSessionID = responseData.getTwoByteValue(0);
245 if (!KLF200Response.check4matchingSessionID(logger, finishedNtfSessionID, reqSessionID)) {
248 logger.debug("setResponse(): finishedNtfSessionID={}.", finishedNtfSessionID);
253 KLF200Response.errorLogging(logger, responseCommand);
256 KLF200Response.outroLogging(logger, success, finished);
260 public boolean isCommunicationFinished() {
265 public boolean isCommunicationSuccessful() {
270 * ===========================================================
271 * Methods in addition to the interface {@link BridgeCommunicationProtocol}
272 * and the abstract class {@link GetProduct}
276 public void setProductId(int nodeId) {
277 logger.trace("setProductId({}) called.", nodeId);
282 public VeluxProduct getProduct() {
283 logger.trace("getProduct(): returning {}.", product);
288 public StatusReply getStatusReply() {