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.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.eclipse.jdt.annotation.NonNullByDefault;
22 import org.eclipse.jdt.annotation.Nullable;
25 * The {@link AM43Command} provides basic functionality that all commands for the AM43 share.
27 * @author Connor Petty - Initial contribution
30 public abstract class AM43Command {
32 private static final byte[] REQUEST_PREFIX = { 0, (byte) 0xFF, 0, 0 };
33 private static final byte HEADER_PREFIX = (byte) 0x9a;
35 private final Lock stateLock = new ReentrantLock();
37 private final Condition stateCondition = stateLock.newCondition();
39 private volatile State state;
45 private byte @Nullable [] response;
47 public AM43Command(byte commandHeader, byte... data) {
48 this.header = commandHeader;
50 this.state = State.NEW;
62 * Returns current state of the command.
64 * @return current state
66 public State getState() {
71 * Sets state of the command.
73 * @param state new state
75 public void setState(State state) {
79 stateCondition.signalAll();
85 public boolean awaitStateChange(long timeout, TimeUnit unit, State... expectedStates) throws InterruptedException {
88 long nanosTimeout = unit.toNanos(timeout);
89 while (!isInAnyState(expectedStates)) {
90 if (nanosTimeout <= 0L) {
93 nanosTimeout = stateCondition.awaitNanos(nanosTimeout);
101 private boolean isInAnyState(State[] acceptedStates) {
102 for (State acceptedState : acceptedStates) {
103 if (acceptedState == state) {
110 public byte getHeader() {
114 public static byte getRequestHeader(byte[] request) {
115 return request[REQUEST_PREFIX.length + 1];
118 public static byte getResponseHeader(byte[] response) {
122 public byte[] getRequest() {
123 byte[] value = new byte[4 + data.length + REQUEST_PREFIX.length];
124 System.arraycopy(REQUEST_PREFIX, 0, value, 0, REQUEST_PREFIX.length);
125 value[REQUEST_PREFIX.length] = HEADER_PREFIX;
126 value[REQUEST_PREFIX.length + 1] = header;
127 value[REQUEST_PREFIX.length + 2] = (byte) data.length;
128 System.arraycopy(data, 0, value, REQUEST_PREFIX.length + 3, data.length);
129 value[value.length - 1] = createChecksum(value, REQUEST_PREFIX.length, 3 + data.length);
134 * A basic method to calculate the checksum
136 * @param data source for the checksum calculation
137 * @param startIndex the zero-based start index to include in the calculation
138 * @param length the length of the range to include in the calculation
139 * @return the CRC-checksum result in {@link byte}
141 protected byte createChecksum(byte[] data, int startIndex, int length) {
142 byte crc = data[startIndex];
143 for (int i = startIndex + 1; i < startIndex + length; i++) {
149 public boolean handleResponse(Executor executor, ResponseListener listener, byte @Nullable [] response) {
150 if (response == null || response.length < minResponseSize()) {
153 if (getResponseHeader(response) != header) {
156 this.response = response;
157 setState(State.SUCCEEDED);
161 public int minResponseSize() {
165 public byte[] getResponse() {
166 byte[] ret = response;
168 throw new IllegalStateException("Response has yet to be received");
174 public String toString() {
175 return getClass().getSimpleName();