2 * Copyright (c) 2010-2021 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.bluetooth.am43.internal.command;
15 import java.util.concurrent.Executor;
16 import java.util.concurrent.TimeUnit;
17 import java.util.concurrent.locks.Condition;
18 import java.util.concurrent.locks.Lock;
19 import java.util.concurrent.locks.ReentrantLock;
21 import org.apache.commons.lang3.ArrayUtils;
22 import org.eclipse.jdt.annotation.NonNullByDefault;
23 import org.eclipse.jdt.annotation.Nullable;
26 * The {@link AM43Command} provides basic functionality that all commands for the AM43 share.
28 * @author Connor Petty - Initial contribution
31 public abstract class AM43Command {
33 private static final byte[] REQUEST_PREFIX = { 0, (byte) 0xFF, 0, 0 };
34 private static final byte HEADER_PREFIX = (byte) 0x9a;
36 private final Lock stateLock = new ReentrantLock();
38 private final Condition stateCondition = stateLock.newCondition();
40 private volatile State state;
46 private byte @Nullable [] response;
48 public AM43Command(byte commandHeader, byte... data) {
49 this.header = commandHeader;
51 this.state = State.NEW;
63 * Returns current state of the command.
65 * @return current state
67 public State getState() {
72 * Sets state of the command.
74 * @param state new state
76 public void setState(State state) {
80 stateCondition.signalAll();
86 public boolean awaitStateChange(long timeout, TimeUnit unit, State... expectedStates) throws InterruptedException {
89 long nanosTimeout = unit.toNanos(timeout);
90 while (!isInAnyState(expectedStates)) {
91 if (nanosTimeout <= 0L) {
94 nanosTimeout = stateCondition.awaitNanos(nanosTimeout);
102 private boolean isInAnyState(State[] acceptedStates) {
103 for (State acceptedState : acceptedStates) {
104 if (acceptedState == state) {
111 public byte getHeader() {
115 public static byte getRequestHeader(byte[] request) {
116 return request[REQUEST_PREFIX.length + 1];
119 public static byte getResponseHeader(byte[] response) {
123 public byte[] getRequest() {
124 byte[] value = ArrayUtils.EMPTY_BYTE_ARRAY;
125 value = ArrayUtils.add(value, HEADER_PREFIX);
126 value = ArrayUtils.add(value, header);
127 value = ArrayUtils.add(value, (byte) data.length);
128 value = ArrayUtils.addAll(value, data);
129 value = ArrayUtils.add(value, createChecksum(value));
130 return ArrayUtils.addAll(REQUEST_PREFIX, value);
133 protected byte createChecksum(byte[] data) {
134 // this is a basic checksum
136 for (int i = 1; i < data.length; i++) {
142 public boolean handleResponse(Executor executor, ResponseListener listener, byte @Nullable [] response) {
143 if (response == null || response.length < minResponseSize()) {
146 if (getResponseHeader(response) != header) {
149 this.response = response;
150 setState(State.SUCCEEDED);
154 public int minResponseSize() {
158 public byte[] getResponse() {
159 byte[] ret = response;
161 throw new IllegalStateException("Response has yet to be received");
167 public String toString() {
168 return getClass().getSimpleName();