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.eclipse.jdt.annotation.Nullable;
19 import org.openhab.binding.velux.internal.bridge.common.RunProductCommand;
20 import org.openhab.binding.velux.internal.bridge.slip.utils.KLF200Response;
21 import org.openhab.binding.velux.internal.bridge.slip.utils.Packet;
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.DataSource;
26 import org.openhab.binding.velux.internal.things.VeluxProduct.ProductBridgeIndex;
27 import org.openhab.binding.velux.internal.things.VeluxProduct.ProductState;
28 import org.openhab.binding.velux.internal.things.VeluxProductName;
29 import org.openhab.binding.velux.internal.things.VeluxProductPosition;
30 import org.slf4j.Logger;
31 import org.slf4j.LoggerFactory;
34 * Protocol specific bridge communication supported by the Velux bridge:
35 * <B>Send Command to Actuator</B>
37 * Common Message semantic: Communication with the bridge and (optionally) storing returned information within the class
40 * As 3rd level class it defines informations how to send query and receive answer through the
41 * {@link org.openhab.binding.velux.internal.bridge.VeluxBridgeProvider VeluxBridgeProvider}
42 * as described by the interface {@link org.openhab.binding.velux.internal.bridge.slip.SlipBridgeCommunicationProtocol
43 * SlipBridgeCommunicationProtocol}.
45 * Methods in addition to the mentioned interface:
47 * <LI>{@link #setNodeAndMainParameter} to define the node and intended parameter value.</LI>
50 * @see RunProductCommand
51 * @see SlipBridgeCommunicationProtocol
53 * @author Guenther Schreiner - Initial contribution.
56 public class SCrunProductCommand extends RunProductCommand implements SlipBridgeCommunicationProtocol {
57 private final Logger logger = LoggerFactory.getLogger(SCrunProductCommand.class);
59 private static final String DESCRIPTION = "Send Command to Actuator";
60 private static final Command COMMAND = Command.GW_COMMAND_SEND_REQ;
63 * ===========================================================
64 * Message Content Parameters
67 private int reqSessionID = 0;
68 private int reqCommandOriginator = 8; // SAAC
69 private int reqPriorityLevel = 5; // Comfort Level 2
70 private int reqParameterActive = 0; // Main Parameter
71 private int reqFPI1 = 0; // Functional Parameter Indicator 1 set of bits
72 private int reqFPI2 = 0; // Functional Parameter Indicator 2 set of bits
73 private int reqMainParameter = 0; // for FunctionalParameterValueArray
74 private int reqIndexArrayCount = 1; // One node will be addressed
75 private int reqIndexArray01 = 1; // This is the node
76 private int reqPriorityLevelLock = 0; // Do not set a new lock on priority level
77 private int reqPL03 = 0; // unused
78 private int reqPL47 = 0; // unused
79 private int reqLockTime = 0; // 30 seconds
80 private @Nullable FunctionalParameters reqFunctionalParameters = null;
83 * ===========================================================
87 private byte[] requestData = new byte[0];
90 * ===========================================================
94 private boolean success = false;
95 private boolean finished = false;
97 private VeluxProduct product = VeluxProduct.UNKNOWN;
100 * ===========================================================
104 public SCrunProductCommand() {
105 logger.debug("SCgetProduct(Constructor) called.");
106 Random rand = new Random();
107 reqSessionID = rand.nextInt(0x0fff);
108 logger.debug("SCgetProduct(): starting sessions with the random number {}.", reqSessionID);
112 * ===========================================================
113 * Methods required for interface {@link BridgeCommunicationProtocol}.
117 public String name() {
122 public CommandNumber getRequestCommand() {
125 logger.debug("getRequestCommand() returns {}.", COMMAND.getCommand());
126 return COMMAND.getCommand();
130 public byte[] getRequestDataAsArrayOfBytes() {
131 Packet request = new Packet(new byte[66]);
132 reqSessionID = (reqSessionID + 1) & 0xffff;
134 request.setTwoByteValue(0, reqSessionID);
135 request.setOneByteValue(2, reqCommandOriginator);
136 request.setOneByteValue(3, reqPriorityLevel);
137 request.setOneByteValue(4, reqParameterActive);
139 FunctionalParameters reqFunctionalParameters = this.reqFunctionalParameters;
140 reqFPI1 = reqFunctionalParameters != null ? reqFunctionalParameters.writeArray(request, 9) : 0;
142 request.setOneByteValue(5, reqFPI1);
143 request.setOneByteValue(6, reqFPI2);
144 request.setTwoByteValue(7, reqMainParameter);
145 request.setOneByteValue(41, reqIndexArrayCount);
146 request.setOneByteValue(42, reqIndexArray01);
147 request.setOneByteValue(62, reqPriorityLevelLock);
148 request.setOneByteValue(63, reqPL03);
149 request.setOneByteValue(64, reqPL47);
150 request.setOneByteValue(65, reqLockTime);
152 requestData = request.toByteArray();
154 if (logger.isTraceEnabled()) {
155 logger.trace("getRequestDataAsArrayOfBytes(): ntfSessionID={}.", hex(reqSessionID));
156 logger.trace("getRequestDataAsArrayOfBytes(): reqCommandOriginator={}.", hex(reqCommandOriginator));
157 logger.trace("getRequestDataAsArrayOfBytes(): reqPriorityLevel={}.", hex(reqPriorityLevel));
158 logger.trace("getRequestDataAsArrayOfBytes(): reqParameterActive={}.", hex(reqParameterActive));
159 logger.trace("getRequestDataAsArrayOfBytes(): reqFPI1={}.", bin(reqFPI1));
160 logger.trace("getRequestDataAsArrayOfBytes(): reqFPI2={}.", bin(reqFPI2));
161 logger.trace("getRequestDataAsArrayOfBytes(): reqMainParameter={}.", hex(reqMainParameter));
162 logger.trace("getRequestDataAsArrayOfBytes(): reqFunctionalParameters={}.", reqFunctionalParameters);
163 logger.trace("getRequestDataAsArrayOfBytes(): reqIndexArrayCount={}.", hex(reqIndexArrayCount));
164 logger.trace("getRequestDataAsArrayOfBytes(): reqIndexArray01={} (reqNodeId={}).", reqIndexArray01,
166 logger.trace("getRequestDataAsArrayOfBytes(): reqPriorityLevelLock={}.", hex(reqPriorityLevelLock));
167 logger.trace("getRequestDataAsArrayOfBytes(): reqPL03={}.", hex(reqPL03));
168 logger.trace("getRequestDataAsArrayOfBytes(): reqPL47={}.", hex(reqPL47));
169 logger.trace("getRequestDataAsArrayOfBytes(): reqLockTime={}.", hex(reqLockTime));
171 logger.trace("getRequestDataAsArrayOfBytes() data is {}.", new Packet(requestData).toString());
176 private String hex(int i) {
177 return Integer.toHexString(i);
180 private String bin(int i) {
181 return Integer.toBinaryString(i);
185 public void setResponse(short responseCommand, byte[] thisResponseData, boolean isSequentialEnforced) {
186 KLF200Response.introLogging(logger, responseCommand, thisResponseData);
189 Packet responseData = new Packet(thisResponseData);
190 switch (Command.get(responseCommand)) {
191 case GW_COMMAND_SEND_CFM:
192 if (!KLF200Response.isLengthValid(logger, responseCommand, thisResponseData, 3)) {
196 int cfmSessionID = responseData.getTwoByteValue(0);
197 int cfmStatus = responseData.getOneByteValue(2);
200 logger.info("setResponse(): returned status: Error – Command rejected.");
204 logger.debug("setResponse(): returned status: OK - Command is accepted.");
205 if (!KLF200Response.check4matchingSessionID(logger, cfmSessionID, reqSessionID)) {
207 } else if (!isSequentialEnforced) {
209 "setResponse(): skipping wait for more packets as sequential processing is not enforced.");
215 logger.warn("setResponse(): returned status={} (not defined).", cfmStatus);
221 case GW_COMMAND_RUN_STATUS_NTF:
222 if (!KLF200Response.isLengthValid(logger, responseCommand, thisResponseData, 13)) {
226 int ntfSessionID = responseData.getTwoByteValue(0);
227 int ntfStatusiD = responseData.getOneByteValue(2);
228 int ntfIndex = responseData.getOneByteValue(3);
229 int ntfNodeParameter = responseData.getOneByteValue(4);
230 int ntfParameterValue = responseData.getTwoByteValue(5);
231 int ntfRunStatus = responseData.getOneByteValue(7);
232 int ntfStatusReply = responseData.getOneByteValue(8);
233 int ntfInformationCode = responseData.getFourByteValue(9);
235 if (logger.isTraceEnabled()) {
236 logger.trace("setResponse(): ntfSessionID={} (requested {}).", ntfSessionID, reqSessionID);
237 logger.trace("setResponse(): ntfStatusiD={}.", ntfStatusiD);
238 logger.trace("setResponse(): ntfIndex={}.", ntfIndex);
239 logger.trace("setResponse(): ntfNodeParameter={}.", ntfNodeParameter);
240 logger.trace("setResponse(): ntfParameterValue={}.", String.format("0x%04X", ntfParameterValue));
241 logger.trace("setResponse(): ntfRunStatus={}.", ntfRunStatus);
242 logger.trace("setResponse(): ntfStatusReply={}.", ntfStatusReply);
243 logger.trace("setResponse(): ntfInformationCode={}.", ntfInformationCode);
246 if (!KLF200Response.check4matchingSessionID(logger, ntfSessionID, reqSessionID)) {
249 switch (ntfRunStatus) {
251 logger.debug("setResponse(): returned ntfRunStatus: EXECUTION_COMPLETED.");
255 logger.info("setResponse(): returned ntfRunStatus: EXECUTION_FAILED.");
259 logger.debug("setResponse(): returned ntfRunStatus: EXECUTION_ACTIVE.");
262 logger.warn("setResponse(): returned ntfRunStatus={} (not defined).", ntfRunStatus);
266 if (!isSequentialEnforced) {
268 "setResponse(): skipping wait for more packets as sequential processing is not enforced.");
274 case GW_COMMAND_REMAINING_TIME_NTF:
275 if (!KLF200Response.isLengthValid(logger, responseCommand, thisResponseData, 6)) {
279 int timeNtfSessionID = responseData.getTwoByteValue(0);
280 int timeNtfIndex = responseData.getOneByteValue(2);
281 int timeNtfNodeParameter = responseData.getOneByteValue(3);
282 int timeNtfSeconds = responseData.getTwoByteValue(4);
284 if (!KLF200Response.check4matchingSessionID(logger, timeNtfSessionID, reqSessionID)) {
288 if (logger.isDebugEnabled()) {
289 logger.debug("setResponse(): timeNtfSessionID={}.", timeNtfSessionID);
290 logger.debug("setResponse(): timeNtfIndex={}.", timeNtfIndex);
291 logger.debug("setResponse(): timeNtfNodeParameter={}.", timeNtfNodeParameter);
292 logger.debug("setResponse(): timeNtfSeconds={}.", timeNtfSeconds);
295 if (!isSequentialEnforced) {
297 "setResponse(): skipping wait for more packets as sequential processing is not enforced.");
303 case GW_SESSION_FINISHED_NTF:
305 if (!KLF200Response.isLengthValid(logger, responseCommand, thisResponseData, 2)) {
308 int finishedNtfSessionID = responseData.getTwoByteValue(0);
309 if (!KLF200Response.check4matchingSessionID(logger, finishedNtfSessionID, reqSessionID)) {
312 logger.debug("setResponse(): finishedNtfSessionID={}.", finishedNtfSessionID);
317 KLF200Response.errorLogging(logger, responseCommand);
320 KLF200Response.outroLogging(logger, success, finished);
324 public boolean isCommunicationFinished() {
329 public boolean isCommunicationSuccessful() {
334 * ===========================================================
335 * Methods in addition to the interface {@link BridgeCommunicationProtocol}
336 * and the abstract class {@link RunProductCommand}
340 public boolean setNodeIdAndParameters(int nodeId, @Nullable VeluxProductPosition mainParameter,
341 @Nullable FunctionalParameters functionalParameters) {
342 logger.debug("setNodeIdAndParameters({}) called.", nodeId);
344 if ((mainParameter != null) || (functionalParameters != null)) {
345 reqIndexArray01 = nodeId;
347 reqMainParameter = (mainParameter == null) ? VeluxProductPosition.VPP_VELUX_STOP
348 : mainParameter.getPositionAsVeluxType();
350 int setMainParameter = VeluxProductPosition.isValid(reqMainParameter) ? reqMainParameter
351 : VeluxProductPosition.VPP_VELUX_IGNORE;
353 reqFunctionalParameters = functionalParameters;
355 // create notification product that clones the new command positions
356 product = new VeluxProduct(VeluxProductName.UNKNOWN, new ProductBridgeIndex(reqIndexArray01),
357 ProductState.EXECUTING.value, setMainParameter, setMainParameter, reqFunctionalParameters, COMMAND)
358 .overrideDataSource(DataSource.BINDING);
362 product = VeluxProduct.UNKNOWN;
366 public VeluxProduct getProduct() {
367 logger.trace("getProduct(): returning {}.", product);