2 * Copyright (c) 2010-2020 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.io.transport.modbus;
15 import java.math.BigInteger;
16 import java.nio.BufferOverflowException;
17 import java.nio.InvalidMarkException;
18 import java.util.Optional;
19 import java.util.concurrent.atomic.AtomicInteger;
20 import java.util.concurrent.atomic.AtomicReference;
22 import org.eclipse.jdt.annotation.NonNullByDefault;
23 import org.eclipse.jdt.annotation.Nullable;
26 * ByteBuffer-like interface for working with different types of data stored in byte arrays
28 * @author Sami Salonen - Initial contribution
31 public class ValueBuffer {
32 private final byte[] bytes;
33 private final AtomicInteger byteIndex = new AtomicInteger();
34 private volatile AtomicReference<@Nullable AtomicInteger> mark = new AtomicReference<>();
37 * Wrap modbus registers and create a new instance of ValueBuffer
39 * The instance will have position of 0.
41 * @param array set of registers
42 * @return new instance of ValueBuffer referencing bytes represented by modbus register array
44 public static ValueBuffer wrap(ModbusRegisterArray array) {
45 return new ValueBuffer(array.getBytes());
49 * Wrap given bytes and create a new instance of ValueBuffer
51 * The instance will have position of 0.
54 * @param array set of bytes to wrap
55 * @return new instance of ValueBuffer referencing bytes
57 public static ValueBuffer wrap(byte[] array) {
58 return new ValueBuffer(array);
61 private ValueBuffer(byte[] bytes) {
66 * Returns this buffer's position.
68 * @return The position of this buffer
70 public int position() {
71 return byteIndex.get();
75 * Sets this buffer's position. If the mark is defined and larger than the new position then it is discarded.
79 public ValueBuffer position(int byteIndex) {
80 this.mark.getAndUpdate(curMark -> {
81 if (curMark == null) {
83 } else if (curMark.get() > byteIndex) {
89 this.byteIndex.set(byteIndex);
94 * Sets this buffer's mark at its position.
98 public ValueBuffer mark() {
99 mark = new AtomicReference<>(new AtomicInteger(byteIndex.get()));
104 * Resets this buffer's position to the previously-marked position.
105 * Invoking this method neither changes nor discards the mark's value.
107 * @return this buffer
108 * @throws InvalidMarkException If the mark has not been set
110 public ValueBuffer reset() throws InvalidMarkException {
111 int mark = Optional.ofNullable(this.mark.get()).map(i -> i.get()).orElse(-1);
113 throw new InvalidMarkException();
120 * Returns the number of bytes between the current position and the end.
122 * @return The number of bytes remaining in this buffer
124 public int remaining() {
125 return bytes.length - byteIndex.get();
129 * Returns underlying bytes
131 * @return reference to underlying bytes
133 public byte[] array() {
138 * Tells whether there are any bytes left between current position and the end
140 * @return true if, and only if, there is at least one byte remaining in this buffer
142 public boolean hasRemaining() {
143 return remaining() > 0;
147 * Starting from current position, read dst.length number of bytes and copy the data to dst
149 * @param dst copied bytes
150 * @return this buffer
151 * @throws BufferOverflowException If there is insufficient space in this buffer for the remaining bytes in the
154 public ValueBuffer get(byte[] dst) {
155 int start = byteIndex.getAndAdd(dst.length);
157 System.arraycopy(bytes, start, dst, 0, dst.length);
158 } catch (IndexOutOfBoundsException e) {
159 throw new BufferOverflowException();
165 * Extract signed 8-bit integer at current position, and advance position.
167 * @return signed 8-bit integer (byte)
168 * @see ModbusBitUtilities.extractSInt8
169 * @throws IllegalArgumentException when there are not enough bytes in this ValueBuffer
171 public byte getSInt8() {
172 return ModbusBitUtilities.extractSInt8(bytes, byteIndex.getAndAdd(1));
176 * Extract unsigned 8-bit integer at current position, and advance position.
178 * @return unsigned 8-bit integer
179 * @see ModbusBitUtilities.extractUInt8
180 * @throws IllegalArgumentException when there are not enough bytes in this ValueBuffer
182 public short getUInt8() {
183 return ModbusBitUtilities.extractUInt8(bytes, byteIndex.getAndAdd(1));
187 * Extract signed 16-bit integer at current position, and advance position.
189 * @return signed 16-bit integer (short)
190 * @see ModbusBitUtilities.extractSInt16
191 * @throws IllegalArgumentException when there are not enough bytes in this ValueBuffer
193 public short getSInt16() {
194 return ModbusBitUtilities.extractSInt16(bytes, byteIndex.getAndAdd(2));
198 * Extract unsigned 16-bit integer at current position, and advance position.
200 * @return unsigned 16-bit integer
201 * @see ModbusBitUtilities.extractUInt16
202 * @throws IllegalArgumentException when there are not enough bytes in this ValueBuffer
204 public int getUInt16() {
205 return ModbusBitUtilities.extractUInt16(bytes, byteIndex.getAndAdd(2));
209 * Extract signed 32-bit integer at current position, and advance position.
211 * @return signed 32-bit integer
212 * @see ModbusBitUtilities.extractSInt32
213 * @throws IllegalArgumentException when there are not enough bytes in this ValueBuffer
215 public int getSInt32() {
216 return ModbusBitUtilities.extractSInt32(bytes, byteIndex.getAndAdd(4));
220 * Extract unsigned 32-bit integer at current position, and advance position.
222 * @return unsigned 32-bit integer
223 * @see ModbusBitUtilities.extractUInt32
224 * @throws IllegalArgumentException when there are not enough bytes in this ValueBuffer
226 public long getUInt32() {
227 return ModbusBitUtilities.extractUInt32(bytes, byteIndex.getAndAdd(4));
231 * Extract signed 32-bit integer at current position, and advance position.
233 * This is identical with getSInt32, but with registers swapped.
235 * @return signed 32-bit integer
236 * @see ModbusBitUtilities.extractSInt32Swap
237 * @throws IllegalArgumentException when there are not enough bytes in this ValueBuffer
239 public int getSInt32Swap() {
240 return ModbusBitUtilities.extractSInt32Swap(bytes, byteIndex.getAndAdd(4));
244 * Extract unsigned 32-bit integer at current position, and advance position.
246 * This is identical with getUInt32, but with registers swapped.
248 * @return unsigned 32-bit integer
249 * @see ModbusBitUtilities.extractUInt32Swap
250 * @throws IllegalArgumentException when there are not enough bytes in this ValueBuffer
252 public long getUInt32Swap() {
253 return ModbusBitUtilities.extractUInt32Swap(bytes, byteIndex.getAndAdd(4));
257 * Extract signed 64-bit integer at current position, and advance position.
259 * @return signed 64-bit integer
260 * @see ModbusBitUtilities.extractInt64
261 * @throws IllegalArgumentException when there are not enough bytes in this ValueBuffer
263 public long getSInt64() {
264 return ModbusBitUtilities.extractSInt64(bytes, byteIndex.getAndAdd(8));
268 * Extract unsigned 64-bit integer at current position, and advance position.
270 * @return unsigned 64-bit integer
271 * @see ModbusBitUtilities.extractUInt64
272 * @throws IllegalArgumentException when there are not enough bytes in this ValueBuffer
274 public BigInteger getUInt64() {
275 return ModbusBitUtilities.extractUInt64(bytes, byteIndex.getAndAdd(8));
279 * Extract signed 64-bit integer at current position, and advance position.
281 * This is identical with getSInt64, but with registers swapped.
283 * @return signed 64-bit integer
284 * @see ModbusBitUtilities.extractInt64Swap
285 * @throws IllegalArgumentException when there are not enough bytes in this ValueBuffer
287 public long getSInt64Swap() {
288 return ModbusBitUtilities.extractSInt64Swap(bytes, byteIndex.getAndAdd(8));
292 * Extract unsigned 64-bit integer at current position, and advance position.
294 * This is identical with getUInt64, but with registers swapped.
296 * @return unsigned 64-bit integer
297 * @see ModbusBitUtilities.extractUInt64Swap
298 * @throws IllegalArgumentException when there are not enough bytes in this ValueBuffer
300 public BigInteger getUInt64Swap() {
301 return ModbusBitUtilities.extractUInt64Swap(bytes, byteIndex.getAndAdd(8));
305 * Extract single-precision 32-bit IEEE 754 floating point at current position, and advance position.
307 * Note that this method can return floating point NaN and floating point infinity.
309 * @return single-precision 32-bit IEEE 754 floating point
310 * @see ModbusBitUtilities.extractFloat32
311 * @throws IllegalArgumentException when there are not enough bytes in this ValueBuffer
313 public float getFloat32() {
314 return ModbusBitUtilities.extractFloat32(bytes, byteIndex.getAndAdd(4));
318 * Extract single-precision 32-bit IEEE 754 floating point at current position, and advance position.
320 * This is identical with getFloat32, but with registers swapped.
322 * Note that this method can return floating point NaN and floating point infinity.
324 * @return single-precision 32-bit IEEE 754 floating point
325 * @see ModbusBitUtilities.extractFloat32
326 * @throws IllegalArgumentException when there are not enough bytes in this ValueBuffer
328 public float getFloat32Swap() {
329 return ModbusBitUtilities.extractFloat32Swap(bytes, byteIndex.getAndAdd(4));