]> git.basschouten.com Git - openhab-addons.git/blob
1204e11b6fd78c4774ddb58bcffd8f12ffc47573
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2021 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
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
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.bluetooth.am43.internal.command;
14
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;
20
21 import org.apache.commons.lang3.ArrayUtils;
22 import org.eclipse.jdt.annotation.NonNullByDefault;
23 import org.eclipse.jdt.annotation.Nullable;
24
25 /**
26  * The {@link AM43Command} provides basic functionality that all commands for the AM43 share.
27  *
28  * @author Connor Petty - Initial contribution
29  */
30 @NonNullByDefault
31 public abstract class AM43Command {
32
33     private static final byte[] REQUEST_PREFIX = { 0, (byte) 0xFF, 0, 0 };
34     private static final byte HEADER_PREFIX = (byte) 0x9a;
35
36     private final Lock stateLock = new ReentrantLock();
37
38     private final Condition stateCondition = stateLock.newCondition();
39
40     private volatile State state;
41
42     private byte header;
43
44     private byte[] data;
45
46     private byte @Nullable [] response;
47
48     public AM43Command(byte commandHeader, byte... data) {
49         this.header = commandHeader;
50         this.data = data;
51         this.state = State.NEW;
52     }
53
54     public enum State {
55         NEW,
56         ENQUEUED,
57         SENT,
58         SUCCEEDED,
59         FAILED
60     }
61
62     /**
63      * Returns current state of the command.
64      *
65      * @return current state
66      */
67     public State getState() {
68         return state;
69     }
70
71     /**
72      * Sets state of the command.
73      *
74      * @param state new state
75      */
76     public void setState(State state) {
77         stateLock.lock();
78         try {
79             this.state = state;
80             stateCondition.signalAll();
81         } finally {
82             stateLock.unlock();
83         }
84     }
85
86     public boolean awaitStateChange(long timeout, TimeUnit unit, State... expectedStates) throws InterruptedException {
87         stateLock.lock();
88         try {
89             long nanosTimeout = unit.toNanos(timeout);
90             while (!isInAnyState(expectedStates)) {
91                 if (nanosTimeout <= 0L) {
92                     return false;
93                 }
94                 nanosTimeout = stateCondition.awaitNanos(nanosTimeout);
95             }
96         } finally {
97             stateLock.unlock();
98         }
99         return true;
100     }
101
102     private boolean isInAnyState(State[] acceptedStates) {
103         for (State acceptedState : acceptedStates) {
104             if (acceptedState == state) {
105                 return true;
106             }
107         }
108         return false;
109     }
110
111     public byte getHeader() {
112         return header;
113     }
114
115     public static byte getRequestHeader(byte[] request) {
116         return request[REQUEST_PREFIX.length + 1];
117     }
118
119     public static byte getResponseHeader(byte[] response) {
120         return response[1];
121     }
122
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);
131     }
132
133     protected byte createChecksum(byte[] data) {
134         // this is a basic checksum
135         byte crc = data[0];
136         for (int i = 1; i < data.length; i++) {
137             crc ^= data[i];
138         }
139         return crc;
140     }
141
142     public boolean handleResponse(Executor executor, ResponseListener listener, byte @Nullable [] response) {
143         if (response == null || response.length < minResponseSize()) {
144             return false;
145         }
146         if (getResponseHeader(response) != header) {
147             return false;
148         }
149         this.response = response;
150         setState(State.SUCCEEDED);
151         return true;
152     }
153
154     public int minResponseSize() {
155         return 2;
156     }
157
158     public byte[] getResponse() {
159         byte[] ret = response;
160         if (ret == null) {
161             throw new IllegalStateException("Response has yet to be received");
162         }
163         return ret;
164     }
165
166     @Override
167     public String toString() {
168         return getClass().getSimpleName();
169     }
170 }