]> git.basschouten.com Git - openhab-addons.git/blob
6980eb68a5164e3cf546d662f0a5abe14ec95b75
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2020 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.io.transport.modbus;
14
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;
21
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;
29
30 /**
31  * Utilities for working with binary data.
32  *
33  * @author Sami Salonen - Initial contribution
34  */
35 @NonNullByDefault
36 public class ModbusBitUtilities {
37
38     /**
39      * Read data from registers and convert the result to DecimalType
40      * Interpretation of <tt>index</tt> goes as follows depending on type
41      *
42      * BIT:
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.
48      * INT8:
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
53      * UINT8:
54      * - same as INT8 except value is interpreted as unsigned integer
55      * INT16:
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
58      * UINT16:
59      * - same as INT16 except value is interpreted as unsigned integer
60      * INT32:
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
64      * INT32_SWAP:
65      * - Same as INT32 but registers swapped
66      * UINT32:
67      * - same as INT32 except value is interpreted as unsigned integer
68      * UINT32_SWAP:
69      * - same as INT32_SWAP except value is interpreted as unsigned integer
70      * FLOAT32:
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
75      * FLOAT32_SWAP:
76      * - Same as FLOAT32 but registers swapped
77      * INT64:
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
81      * INT64_SWAP:
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
84      * UINT64:
85      * - same as INT64 except value is interpreted as unsigned integer
86      * UINT64_SWAP:
87      * - same as INT64_SWAP except value is interpreted as unsigned integer
88      *
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
92      *            from.
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
98      *             bug
99      * @throws IllegalArgumentException when <tt>index</tt> is out of bounds of registers
100      *
101      */
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,
110                             registers.size()));
111         }
112         switch (type) {
113             case BIT:
114                 return Optional
115                         .of(new DecimalType((registers.getRegister(index / 16).toUnsignedShort() >> (index % 16)) & 1));
116             case INT8:
117                 return Optional.of(new DecimalType(registers.getRegister(index / 2).getBytes()[1 - (index % 2)]));
118             case UINT8:
119                 return Optional.of(new DecimalType(
120                         (registers.getRegister(index / 2).toUnsignedShort() >> (8 * (index % 2))) & 0xff));
121             case INT16: {
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)));
125             }
126             case UINT16:
127                 return Optional.of(new DecimalType(registers.getRegister(index).toUnsignedShort()));
128             case INT32: {
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)));
133             }
134             case UINT32: {
135                 ByteBuffer buff = ByteBuffer.allocate(8);
136                 buff.position(4);
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)));
140             }
141             case FLOAT32: {
142                 ByteBuffer buff = ByteBuffer.allocate(4);
143                 buff.put(registers.getRegister(index).getBytes());
144                 buff.put(registers.getRegister(index + 1).getBytes());
145                 try {
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();
150                 }
151             }
152             case INT64: {
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)));
159             }
160             case UINT64: {
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());
166                 return Optional.of(
167                         new DecimalType(new BigDecimal(new BigInteger(1, buff.order(ByteOrder.BIG_ENDIAN).array()))));
168             }
169             case INT32_SWAP: {
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)));
174             }
175             case UINT32_SWAP: {
176                 ByteBuffer buff = ByteBuffer.allocate(8);
177                 buff.position(4);
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)));
181             }
182             case FLOAT32_SWAP: {
183                 ByteBuffer buff = ByteBuffer.allocate(4);
184                 buff.put(registers.getRegister(index + 1).getBytes());
185                 buff.put(registers.getRegister(index).getBytes());
186                 try {
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();
191                 }
192             }
193             case INT64_SWAP: {
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)));
200             }
201             case UINT64_SWAP: {
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());
207                 return Optional.of(
208                         new DecimalType(new BigDecimal(new BigInteger(1, buff.order(ByteOrder.BIG_ENDIAN).array()))));
209             }
210             default:
211                 throw new IllegalArgumentException(type.getConfigValue());
212         }
213     }
214
215     /**
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.
222      *
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
230      */
231     public static StringType extractStringFromRegisters(ModbusRegisterArray registers, int index, int length,
232             Charset charset) {
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,
236                             registers.size()));
237         }
238         if (index < 0) {
239             throw new IllegalArgumentException("Negative index values are not supported");
240         }
241         if (length < 0) {
242             throw new IllegalArgumentException("Negative string length is not supported");
243         }
244         byte[] buff = new byte[length];
245
246         int src = index;
247         int dest;
248         for (dest = 0; dest < length; dest++) {
249
250             byte chr;
251             if (dest % 2 == 0) {
252                 chr = (byte) ((registers.getRegister(src).getValue() >> 8));
253             } else {
254                 chr = (byte) (registers.getRegister(src).getValue() & 0xff);
255                 src++;
256             }
257             if (chr == 0) {
258                 break;
259             }
260             buff[dest] = chr;
261         }
262         return new StringType(new String(buff, 0, dest, charset));
263     }
264
265     /**
266      * Convert command to array of registers using a specific value type
267      *
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
273      */
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)
278                     : DecimalType.ZERO;
279         } else if (command instanceof DecimalType) {
280             numericCommand = (DecimalType) command;
281         } else {
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()));
285         }
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()));
289         }
290         switch (type) {
291             case INT16:
292             case UINT16: {
293                 short shortValue = numericCommand.shortValue();
294                 // big endian byte ordering
295                 byte b1 = (byte) (shortValue >> 8);
296                 byte b2 = (byte) shortValue;
297
298                 ModbusRegister register = new ModbusRegister(b1, b2);
299                 return new ModbusRegisterArray(new ModbusRegister[] { register });
300             }
301             case INT32:
302             case UINT32: {
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 });
312             }
313             case INT32_SWAP:
314             case UINT32_SWAP: {
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 });
324             }
325             case FLOAT32: {
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 });
336             }
337             case FLOAT32_SWAP: {
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 });
348             }
349             case INT64:
350             case UINT64: {
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) });
363             }
364             case INT64_SWAP:
365             case UINT64_SWAP: {
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) });
378             }
379             default:
380                 throw new NotImplementedException(
381                         String.format("Illegal type=%s. Missing implementation for this type", type));
382         }
383     }
384
385     /**
386      * Converts command to a boolean
387      *
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.
391      *
392      * @param command to convert to boolean
393      * @return Boolean value matching the command. Empty if command cannot be converted
394      */
395     public static Optional<Boolean> translateCommand2Boolean(Command command) {
396         if (command.equals(OnOffType.ON)) {
397             return Optional.of(Boolean.TRUE);
398         }
399         if (command.equals(OnOffType.OFF)) {
400             return Optional.of(Boolean.FALSE);
401         }
402         if (command.equals(OpenClosedType.OPEN)) {
403             return Optional.of(Boolean.TRUE);
404         }
405         if (command.equals(OpenClosedType.CLOSED)) {
406             return Optional.of(Boolean.FALSE);
407         }
408         if (command instanceof DecimalType) {
409             return Optional.of(!command.equals(DecimalType.ZERO));
410         }
411         return Optional.empty();
412     }
413 }