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