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.BigDecimal;
16 import java.math.BigInteger;
17 import java.nio.ByteBuffer;
18 import java.nio.ByteOrder;
19 import java.nio.charset.Charset;
20 import java.util.Optional;
22 import org.apache.commons.lang.NotImplementedException;
23 import org.eclipse.jdt.annotation.NonNullByDefault;
24 import org.openhab.core.library.types.DecimalType;
25 import org.openhab.core.library.types.OnOffType;
26 import org.openhab.core.library.types.OpenClosedType;
27 import org.openhab.core.library.types.StringType;
28 import org.openhab.core.types.Command;
31 * Utilities for working with binary data.
33 * @author Sami Salonen - Initial contribution
36 public class ModbusBitUtilities {
39 * Read data from registers and convert the result to DecimalType
40 * Interpretation of <tt>index</tt> goes as follows depending on type
43 * - a single bit is read from the registers
44 * - indices between 0...15 (inclusive) represent bits of the first register
45 * - indices between 16...31 (inclusive) represent bits of the second register, etc.
46 * - index 0 refers to the least significant bit of the first register
47 * - index 1 refers to the second least significant bit of the first register, etc.
49 * - a byte (8 bits) from the registers is interpreted as signed integer
50 * - index 0 refers to low byte of the first register, 1 high byte of first register
51 * - index 2 refers to low byte of the second register, 3 high byte of second register, etc.
52 * - it is assumed that each high and low byte is encoded in most significant bit first order
54 * - same as INT8 except value is interpreted as unsigned integer
56 * - register with index (counting from zero) is interpreted as 16 bit signed integer.
57 * - it is assumed that each register is encoded in most significant bit first order
59 * - same as INT16 except value is interpreted as unsigned integer
61 * - registers (index) and (index + 1) are interpreted as signed 32bit integer.
62 * - it assumed that the first register contains the most significant 16 bits
63 * - it is assumed that each register is encoded in most significant bit first order
65 * - Same as INT32 but registers swapped
67 * - same as INT32 except value is interpreted as unsigned integer
69 * - same as INT32_SWAP except value is interpreted as unsigned integer
71 * - registers (index) and (index + 1) are interpreted as signed 32bit floating point number.
72 * - it assumed that the first register contains the most significant 16 bits
73 * - it is assumed that each register is encoded in most significant bit first order
74 * - floating point NaN and infinity will return as empty optional
76 * - Same as FLOAT32 but registers swapped
78 * - registers (index), (index + 1), (index + 2), (index + 3) are interpreted as signed 64bit integer.
79 * - it assumed that the first register contains the most significant 16 bits
80 * - it is assumed that each register is encoded in most significant bit first order
82 * - same as INT64 but registers swapped, that is, registers (index + 3), (index + 2), (index + 1), (index + 1) are
83 * interpreted as signed 64bit integer
85 * - same as INT64 except value is interpreted as unsigned integer
87 * - same as INT64_SWAP except value is interpreted as unsigned integer
89 * @param registers list of registers, each register represent 16bit of data
90 * @param index zero based item index. Interpretation of this depends on type, see examples above.
91 * With type larger or equal to 16 bits, the index tells the register index to start reading
93 * With type less than 16 bits, the index tells the N'th item to read from the registers.
94 * @param type item type, e.g. unsigned 16bit integer (<tt>ModbusBindingProvider.ValueType.UINT16</tt>)
95 * @return number representation queried value, <tt>DecimalType</tt>. Empty optional is returned
96 * with NaN and infinity floating point values
97 * @throws NotImplementedException in cases where implementation is lacking for the type. This can be considered a
99 * @throws IllegalArgumentException when <tt>index</tt> is out of bounds of registers
102 public static Optional<DecimalType> extractStateFromRegisters(ModbusRegisterArray registers, int index,
103 ModbusConstants.ValueType type) {
104 int endBitIndex = (type.getBits() >= 16 ? 16 * index : type.getBits() * index) + type.getBits() - 1;
105 // each register has 16 bits
106 int lastValidIndex = registers.size() * 16 - 1;
107 if (endBitIndex > lastValidIndex || index < 0) {
108 throw new IllegalArgumentException(
109 String.format("Index=%d with type=%s is out-of-bounds given registers of size %d", index, type,
115 .of(new DecimalType((registers.getRegister(index / 16).toUnsignedShort() >> (index % 16)) & 1));
117 return Optional.of(new DecimalType(registers.getRegister(index / 2).getBytes()[1 - (index % 2)]));
119 return Optional.of(new DecimalType(
120 (registers.getRegister(index / 2).toUnsignedShort() >> (8 * (index % 2))) & 0xff));
122 ByteBuffer buff = ByteBuffer.allocate(2);
123 buff.put(registers.getRegister(index).getBytes());
124 return Optional.of(new DecimalType(buff.order(ByteOrder.BIG_ENDIAN).getShort(0)));
127 return Optional.of(new DecimalType(registers.getRegister(index).toUnsignedShort()));
129 ByteBuffer buff = ByteBuffer.allocate(4);
130 buff.put(registers.getRegister(index).getBytes());
131 buff.put(registers.getRegister(index + 1).getBytes());
132 return Optional.of(new DecimalType(buff.order(ByteOrder.BIG_ENDIAN).getInt(0)));
135 ByteBuffer buff = ByteBuffer.allocate(8);
137 buff.put(registers.getRegister(index).getBytes());
138 buff.put(registers.getRegister(index + 1).getBytes());
139 return Optional.of(new DecimalType(buff.order(ByteOrder.BIG_ENDIAN).getLong(0)));
142 ByteBuffer buff = ByteBuffer.allocate(4);
143 buff.put(registers.getRegister(index).getBytes());
144 buff.put(registers.getRegister(index + 1).getBytes());
146 return Optional.of(new DecimalType(buff.order(ByteOrder.BIG_ENDIAN).getFloat(0)));
147 } catch (NumberFormatException e) {
148 // floating point NaN or infinity encountered
149 return Optional.empty();
153 ByteBuffer buff = ByteBuffer.allocate(8);
154 buff.put(registers.getRegister(index).getBytes());
155 buff.put(registers.getRegister(index + 1).getBytes());
156 buff.put(registers.getRegister(index + 2).getBytes());
157 buff.put(registers.getRegister(index + 3).getBytes());
158 return Optional.of(new DecimalType(buff.order(ByteOrder.BIG_ENDIAN).getLong(0)));
161 ByteBuffer buff = ByteBuffer.allocate(8);
162 buff.put(registers.getRegister(index).getBytes());
163 buff.put(registers.getRegister(index + 1).getBytes());
164 buff.put(registers.getRegister(index + 2).getBytes());
165 buff.put(registers.getRegister(index + 3).getBytes());
167 new DecimalType(new BigDecimal(new BigInteger(1, buff.order(ByteOrder.BIG_ENDIAN).array()))));
170 ByteBuffer buff = ByteBuffer.allocate(4);
171 buff.put(registers.getRegister(index + 1).getBytes());
172 buff.put(registers.getRegister(index).getBytes());
173 return Optional.of(new DecimalType(buff.order(ByteOrder.BIG_ENDIAN).getInt(0)));
176 ByteBuffer buff = ByteBuffer.allocate(8);
178 buff.put(registers.getRegister(index + 1).getBytes());
179 buff.put(registers.getRegister(index).getBytes());
180 return Optional.of(new DecimalType(buff.order(ByteOrder.BIG_ENDIAN).getLong(0)));
183 ByteBuffer buff = ByteBuffer.allocate(4);
184 buff.put(registers.getRegister(index + 1).getBytes());
185 buff.put(registers.getRegister(index).getBytes());
187 return Optional.of(new DecimalType(buff.order(ByteOrder.BIG_ENDIAN).getFloat(0)));
188 } catch (NumberFormatException e) {
189 // floating point NaN or infinity encountered
190 return Optional.empty();
194 ByteBuffer buff = ByteBuffer.allocate(8);
195 buff.put(registers.getRegister(index + 3).getBytes());
196 buff.put(registers.getRegister(index + 2).getBytes());
197 buff.put(registers.getRegister(index + 1).getBytes());
198 buff.put(registers.getRegister(index).getBytes());
199 return Optional.of(new DecimalType(buff.order(ByteOrder.BIG_ENDIAN).getLong(0)));
202 ByteBuffer buff = ByteBuffer.allocate(8);
203 buff.put(registers.getRegister(index + 3).getBytes());
204 buff.put(registers.getRegister(index + 2).getBytes());
205 buff.put(registers.getRegister(index + 1).getBytes());
206 buff.put(registers.getRegister(index).getBytes());
208 new DecimalType(new BigDecimal(new BigInteger(1, buff.order(ByteOrder.BIG_ENDIAN).array()))));
211 throw new IllegalArgumentException(type.getConfigValue());
216 * Read data from registers and convert the result to StringType
217 * Strings should start the the first byte of a register, but could
218 * have an odd number of characters.
219 * Raw byte array values are converted using the charset parameter
220 * and a maximum of length bytes are read. However reading stops at the first
221 * NUL byte encountered.
223 * @param registers list of registers, each register represent 16bit of data
224 * @param index zero based register index. Registers are handled as 16bit registers,
225 * this parameter defines the starting register.
226 * @param length maximum length of string in 8bit characters.
227 * @param charset the character set used to construct the string.
228 * @return string representation queried value
229 * @throws IllegalArgumentException when <tt>index</tt> is out of bounds of registers
231 public static StringType extractStringFromRegisters(ModbusRegisterArray registers, int index, int length,
233 if (index * 2 + length > registers.size() * 2) {
234 throw new IllegalArgumentException(
235 String.format("Index=%d with length=%d is out-of-bounds given registers of size %d", index, length,
239 throw new IllegalArgumentException("Negative index values are not supported");
242 throw new IllegalArgumentException("Negative string length is not supported");
244 byte[] buff = new byte[length];
248 for (dest = 0; dest < length; dest++) {
252 chr = (byte) ((registers.getRegister(src).getValue() >> 8));
254 chr = (byte) (registers.getRegister(src).getValue() & 0xff);
262 return new StringType(new String(buff, 0, dest, charset));
266 * Convert command to array of registers using a specific value type
268 * @param command command to be converted
269 * @param type value type to use in conversion
270 * @return array of registers
271 * @throws NotImplementedException in cases where implementation is lacking for the type. This is thrown with 1-bit
272 * and 8-bit value types
274 public static ModbusRegisterArray commandToRegisters(Command command, ModbusConstants.ValueType type) {
275 DecimalType numericCommand;
276 if (command instanceof OnOffType || command instanceof OpenClosedType) {
277 numericCommand = translateCommand2Boolean(command).get() ? new DecimalType(BigDecimal.ONE)
279 } else if (command instanceof DecimalType) {
280 numericCommand = (DecimalType) command;
282 throw new NotImplementedException(String.format(
283 "Command '%s' of class '%s' cannot be converted to registers. Please use OnOffType, OpenClosedType, or DecimalType commands.",
284 command, command.getClass().getName()));
286 if (type.getBits() != 16 && type.getBits() != 32 && type.getBits() != 64) {
287 throw new IllegalArgumentException(String.format(
288 "Illegal type=%s (bits=%d). Only 16bit and 32bit types are supported", type, type.getBits()));
293 short shortValue = numericCommand.shortValue();
294 // big endian byte ordering
295 byte b1 = (byte) (shortValue >> 8);
296 byte b2 = (byte) shortValue;
298 ModbusRegister register = new ModbusRegister(b1, b2);
299 return new ModbusRegisterArray(new ModbusRegister[] { register });
303 int intValue = numericCommand.intValue();
304 // big endian byte ordering
305 byte b1 = (byte) (intValue >> 24);
306 byte b2 = (byte) (intValue >> 16);
307 byte b3 = (byte) (intValue >> 8);
308 byte b4 = (byte) intValue;
309 ModbusRegister register = new ModbusRegister(b1, b2);
310 ModbusRegister register2 = new ModbusRegister(b3, b4);
311 return new ModbusRegisterArray(new ModbusRegister[] { register, register2 });
315 int intValue = numericCommand.intValue();
316 // big endian byte ordering
317 byte b1 = (byte) (intValue >> 24);
318 byte b2 = (byte) (intValue >> 16);
319 byte b3 = (byte) (intValue >> 8);
320 byte b4 = (byte) intValue;
321 ModbusRegister register = new ModbusRegister(b3, b4);
322 ModbusRegister register2 = new ModbusRegister(b1, b2);
323 return new ModbusRegisterArray(new ModbusRegister[] { register, register2 });
326 float floatValue = numericCommand.floatValue();
327 int intBits = Float.floatToIntBits(floatValue);
328 // big endian byte ordering
329 byte b1 = (byte) (intBits >> 24);
330 byte b2 = (byte) (intBits >> 16);
331 byte b3 = (byte) (intBits >> 8);
332 byte b4 = (byte) intBits;
333 ModbusRegister register = new ModbusRegister(b1, b2);
334 ModbusRegister register2 = new ModbusRegister(b3, b4);
335 return new ModbusRegisterArray(new ModbusRegister[] { register, register2 });
338 float floatValue = numericCommand.floatValue();
339 int intBits = Float.floatToIntBits(floatValue);
340 // big endian byte ordering
341 byte b1 = (byte) (intBits >> 24);
342 byte b2 = (byte) (intBits >> 16);
343 byte b3 = (byte) (intBits >> 8);
344 byte b4 = (byte) intBits;
345 ModbusRegister register = new ModbusRegister(b3, b4);
346 ModbusRegister register2 = new ModbusRegister(b1, b2);
347 return new ModbusRegisterArray(new ModbusRegister[] { register, register2 });
351 long longValue = numericCommand.longValue();
352 // big endian byte ordering
353 byte b1 = (byte) (longValue >> 56);
354 byte b2 = (byte) (longValue >> 48);
355 byte b3 = (byte) (longValue >> 40);
356 byte b4 = (byte) (longValue >> 32);
357 byte b5 = (byte) (longValue >> 24);
358 byte b6 = (byte) (longValue >> 16);
359 byte b7 = (byte) (longValue >> 8);
360 byte b8 = (byte) longValue;
361 return new ModbusRegisterArray(new ModbusRegister[] { new ModbusRegister(b1, b2),
362 new ModbusRegister(b3, b4), new ModbusRegister(b5, b6), new ModbusRegister(b7, b8) });
366 long longValue = numericCommand.longValue();
367 // big endian byte ordering
368 byte b1 = (byte) (longValue >> 56);
369 byte b2 = (byte) (longValue >> 48);
370 byte b3 = (byte) (longValue >> 40);
371 byte b4 = (byte) (longValue >> 32);
372 byte b5 = (byte) (longValue >> 24);
373 byte b6 = (byte) (longValue >> 16);
374 byte b7 = (byte) (longValue >> 8);
375 byte b8 = (byte) longValue;
376 return new ModbusRegisterArray(new ModbusRegister[] { new ModbusRegister(b7, b8),
377 new ModbusRegister(b5, b6), new ModbusRegister(b3, b4), new ModbusRegister(b1, b2) });
380 throw new NotImplementedException(
381 String.format("Illegal type=%s. Missing implementation for this type", type));
386 * Converts command to a boolean
388 * true value is represented by {@link OnOffType.ON}, {@link OpenClosedType.OPEN}.
389 * false value is represented by {@link OnOffType.OFF}, {@link OpenClosedType.CLOSED}.
390 * Furthermore, {@link DecimalType} are converted to boolean true if they are unequal to zero.
392 * @param command to convert to boolean
393 * @return Boolean value matching the command. Empty if command cannot be converted
395 public static Optional<Boolean> translateCommand2Boolean(Command command) {
396 if (command.equals(OnOffType.ON)) {
397 return Optional.of(Boolean.TRUE);
399 if (command.equals(OnOffType.OFF)) {
400 return Optional.of(Boolean.FALSE);
402 if (command.equals(OpenClosedType.OPEN)) {
403 return Optional.of(Boolean.TRUE);
405 if (command.equals(OpenClosedType.CLOSED)) {
406 return Optional.of(Boolean.FALSE);
408 if (command instanceof DecimalType) {
409 return Optional.of(!command.equals(DecimalType.ZERO));
411 return Optional.empty();