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.internal;
15 import java.util.concurrent.atomic.AtomicBoolean;
16 import java.util.concurrent.atomic.AtomicReference;
17 import java.util.stream.Collectors;
18 import java.util.stream.IntStream;
20 import org.eclipse.jdt.annotation.NonNull;
21 import org.eclipse.jdt.annotation.NonNullByDefault;
22 import org.openhab.io.transport.modbus.AsyncModbusReadResult;
23 import org.openhab.io.transport.modbus.BitArray;
24 import org.openhab.io.transport.modbus.ModbusReadCallback;
25 import org.openhab.io.transport.modbus.ModbusReadFunctionCode;
26 import org.openhab.io.transport.modbus.ModbusReadRequestBlueprint;
27 import org.openhab.io.transport.modbus.ModbusRegisterArray;
28 import org.openhab.io.transport.modbus.ModbusWriteCoilRequestBlueprint;
29 import org.openhab.io.transport.modbus.ModbusWriteRegisterRequestBlueprint;
30 import org.openhab.io.transport.modbus.ModbusWriteRequestBlueprint;
31 import org.openhab.io.transport.modbus.ModbusWriteRequestBlueprintVisitor;
32 import org.openhab.io.transport.modbus.endpoint.ModbusSerialSlaveEndpoint;
33 import org.openhab.io.transport.modbus.endpoint.ModbusSlaveEndpoint;
34 import org.openhab.io.transport.modbus.endpoint.ModbusSlaveEndpointVisitor;
35 import org.openhab.io.transport.modbus.endpoint.ModbusTCPSlaveEndpoint;
36 import org.openhab.io.transport.modbus.endpoint.ModbusUDPSlaveEndpoint;
37 import org.slf4j.Logger;
38 import org.slf4j.LoggerFactory;
40 import net.wimpi.modbus.io.ModbusSerialTransaction;
41 import net.wimpi.modbus.io.ModbusTCPTransaction;
42 import net.wimpi.modbus.io.ModbusTransaction;
43 import net.wimpi.modbus.io.ModbusUDPTransaction;
44 import net.wimpi.modbus.msg.ModbusRequest;
45 import net.wimpi.modbus.msg.ModbusResponse;
46 import net.wimpi.modbus.msg.ReadCoilsRequest;
47 import net.wimpi.modbus.msg.ReadCoilsResponse;
48 import net.wimpi.modbus.msg.ReadInputDiscretesRequest;
49 import net.wimpi.modbus.msg.ReadInputDiscretesResponse;
50 import net.wimpi.modbus.msg.ReadInputRegistersRequest;
51 import net.wimpi.modbus.msg.ReadInputRegistersResponse;
52 import net.wimpi.modbus.msg.ReadMultipleRegistersRequest;
53 import net.wimpi.modbus.msg.ReadMultipleRegistersResponse;
54 import net.wimpi.modbus.msg.WriteCoilRequest;
55 import net.wimpi.modbus.msg.WriteMultipleCoilsRequest;
56 import net.wimpi.modbus.msg.WriteMultipleRegistersRequest;
57 import net.wimpi.modbus.msg.WriteSingleRegisterRequest;
58 import net.wimpi.modbus.net.ModbusSlaveConnection;
59 import net.wimpi.modbus.net.SerialConnection;
60 import net.wimpi.modbus.net.TCPMasterConnection;
61 import net.wimpi.modbus.net.UDPMasterConnection;
62 import net.wimpi.modbus.procimg.InputRegister;
63 import net.wimpi.modbus.procimg.Register;
64 import net.wimpi.modbus.procimg.SimpleInputRegister;
65 import net.wimpi.modbus.util.BitVector;
68 * Conversion utilities between underlying Modbus library (net.wimpi.modbus) and this transport bundle
70 * @author Sami Salonen - Initial contribution
74 public class ModbusLibraryWrapper {
76 private static Logger getLogger() {
77 return LoggerFactory.getLogger(ModbusLibraryWrapper.class);
80 private static BitArray bitArrayFromBitVector(BitVector bitVector, int count) {
81 boolean[] bits = new boolean[count];
82 for (int i = 0; i < count; i++) {
83 bits[i] = bitVector.getBit(i);
85 return new BitArray(bits);
88 private static ModbusRegisterArray modbusRegisterArrayFromInputRegisters(InputRegister[] inputRegisters) {
89 int[] registers = new int[inputRegisters.length];
90 for (int i = 0; i < inputRegisters.length; i++) {
91 registers[i] = inputRegisters[i].getValue();
93 return new ModbusRegisterArray(registers);
97 * Convert the general request to Modbus library request object
100 * @throws IllegalArgumentException
101 * 1) in case function code implies coil data but we have registers
102 * 2) in case function code implies register data but we have coils
103 * 3) in case there is no data
104 * 4) in case there is too much data in case of WRITE_COIL or WRITE_SINGLE_REGISTER
105 * @throws IllegalStateException unexpected function code. Implementation is lacking and this can be considered a
107 * @return MODBUS library request matching the write request
109 public static ModbusRequest createRequest(ModbusWriteRequestBlueprint message) {
110 // ModbusRequest[] request = new ModbusRequest[1];
111 AtomicReference<ModbusRequest> request = new AtomicReference<>();
112 AtomicBoolean writeSingle = new AtomicBoolean(false);
113 switch (message.getFunctionCode()) {
115 writeSingle.set(true);
116 // fall-through on purpose
117 case WRITE_MULTIPLE_COILS:
118 message.accept(new ModbusWriteRequestBlueprintVisitor() {
121 public void visit(ModbusWriteRegisterRequestBlueprint blueprint) {
122 throw new IllegalArgumentException();
126 public void visit(ModbusWriteCoilRequestBlueprint blueprint) {
127 BitArray coils = blueprint.getCoils();
128 if (coils.size() == 0) {
129 throw new IllegalArgumentException("Must provide at least one coil");
131 if (writeSingle.get()) {
132 if (coils.size() != 1) {
133 throw new IllegalArgumentException("Must provide single coil with WRITE_COIL");
135 request.set(new WriteCoilRequest(message.getReference(), coils.getBit(0)));
137 request.set(new WriteMultipleCoilsRequest(message.getReference(),
138 ModbusLibraryWrapper.convertBits(coils)));
143 case WRITE_SINGLE_REGISTER:
144 writeSingle.set(true);
145 // fall-through on purpose
146 case WRITE_MULTIPLE_REGISTERS:
147 message.accept(new ModbusWriteRequestBlueprintVisitor() {
150 public void visit(ModbusWriteRegisterRequestBlueprint blueprint) {
151 Register[] registers = ModbusLibraryWrapper.convertRegisters(blueprint.getRegisters());
152 if (registers.length == 0) {
153 throw new IllegalArgumentException("Must provide at least one register");
155 if (writeSingle.get()) {
156 if (blueprint.getRegisters().size() != 1) {
157 throw new IllegalArgumentException(
158 "Must provide single register with WRITE_SINGLE_REGISTER");
160 request.set(new WriteSingleRegisterRequest(message.getReference(), registers[0]));
162 request.set(new WriteMultipleRegistersRequest(message.getReference(), registers));
167 public void visit(ModbusWriteCoilRequestBlueprint blueprint) {
168 throw new IllegalArgumentException();
173 getLogger().error("Unexpected function code {}", message.getFunctionCode());
174 throw new IllegalStateException(
175 String.format("Unexpected function code %s", message.getFunctionCode()));
177 ModbusRequest modbusRequest = request.get();
178 modbusRequest.setUnitID(message.getUnitID());
179 modbusRequest.setProtocolID(message.getProtocolID());
180 return modbusRequest;
184 * Create a fresh transaction for the given endpoint and connection
186 * The retries of the transaction will be disabled.
192 public static ModbusTransaction createTransactionForEndpoint(ModbusSlaveEndpoint endpoint,
193 ModbusSlaveConnection connection) {
194 ModbusTransaction transaction = endpoint.accept(new ModbusSlaveEndpointVisitor<ModbusTransaction>() {
197 public @NonNull ModbusTransaction visit(ModbusTCPSlaveEndpoint modbusIPSlavePoolingKey) {
198 ModbusTCPTransaction transaction = new ModbusTCPTransaction();
199 transaction.setReconnecting(false);
204 public @NonNull ModbusTransaction visit(ModbusSerialSlaveEndpoint modbusSerialSlavePoolingKey) {
205 return new ModbusSerialTransaction();
209 public @NonNull ModbusTransaction visit(ModbusUDPSlaveEndpoint modbusUDPSlavePoolingKey) {
210 return new ModbusUDPTransaction();
213 // We disable modbus library retries and handle in the Manager implementation
214 transaction.setRetries(0);
215 transaction.setRetryDelayMillis(0);
216 if (transaction instanceof ModbusSerialTransaction) {
217 ((ModbusSerialTransaction) transaction).setSerialConnection((SerialConnection) connection);
218 } else if (transaction instanceof ModbusUDPTransaction) {
219 ((ModbusUDPTransaction) transaction).setTerminal(((UDPMasterConnection) connection).getTerminal());
220 } else if (transaction instanceof ModbusTCPTransaction) {
221 ((ModbusTCPTransaction) transaction).setConnection((TCPMasterConnection) connection);
223 throw new IllegalStateException();
229 * Create fresh request corresponding to {@link ModbusReadRequestBlueprint}
234 public static ModbusRequest createRequest(ModbusReadRequestBlueprint message) {
235 ModbusRequest request;
236 if (message.getFunctionCode() == ModbusReadFunctionCode.READ_COILS) {
237 request = new ReadCoilsRequest(message.getReference(), message.getDataLength());
238 } else if (message.getFunctionCode() == ModbusReadFunctionCode.READ_INPUT_DISCRETES) {
239 request = new ReadInputDiscretesRequest(message.getReference(), message.getDataLength());
240 } else if (message.getFunctionCode() == ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS) {
241 request = new ReadMultipleRegistersRequest(message.getReference(), message.getDataLength());
242 } else if (message.getFunctionCode() == ModbusReadFunctionCode.READ_INPUT_REGISTERS) {
243 request = new ReadInputRegistersRequest(message.getReference(), message.getDataLength());
245 throw new IllegalArgumentException(String.format("Unexpected function code %s", message.getFunctionCode()));
247 request.setUnitID(message.getUnitID());
248 request.setProtocolID(message.getProtocolID());
254 * Convert {@link BitArray} to {@link BitVector}
259 public static BitVector convertBits(BitArray bits) {
260 BitVector bitVector = new BitVector(bits.size());
261 IntStream.range(0, bits.size()).forEach(i -> bitVector.setBit(i, bits.getBit(i)));
266 * Convert {@link ModbusRegisterArray} to array of {@link Register}
271 public static Register[] convertRegisters(ModbusRegisterArray arr) {
272 return IntStream.range(0, arr.size()).mapToObj(i -> new SimpleInputRegister(arr.getRegister(i)))
273 .collect(Collectors.toList()).toArray(new Register[0]);
277 * Get number of bits/registers/discrete inputs in the request.
284 public static int getNumberOfItemsInResponse(ModbusResponse response, ModbusReadRequestBlueprint request) {
285 // jamod library seems to be a bit buggy when it comes number of coils/discrete inputs in the response. Some
286 // of the methods such as ReadCoilsResponse.getBitCount() are returning wrong values.
288 // This is the reason we use a bit more verbose way to get the number of items in the response.
289 final int responseCount;
290 if (request.getFunctionCode() == ModbusReadFunctionCode.READ_COILS) {
291 responseCount = ((ReadCoilsResponse) response).getCoils().size();
292 } else if (request.getFunctionCode() == ModbusReadFunctionCode.READ_INPUT_DISCRETES) {
293 responseCount = ((ReadInputDiscretesResponse) response).getDiscretes().size();
294 } else if (request.getFunctionCode() == ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS) {
295 responseCount = ((ReadMultipleRegistersResponse) response).getRegisters().length;
296 } else if (request.getFunctionCode() == ModbusReadFunctionCode.READ_INPUT_REGISTERS) {
297 responseCount = ((ReadInputRegistersResponse) response).getRegisters().length;
299 throw new IllegalArgumentException(String.format("Unexpected function code %s", request.getFunctionCode()));
301 return responseCount;
305 * Invoke callback with the data received
307 * @param message original request
308 * @param callback callback for read
309 * @param response Modbus library response object
311 public static void invokeCallbackWithResponse(ModbusReadRequestBlueprint request, ModbusReadCallback callback,
312 ModbusResponse response) {
314 getLogger().trace("Calling read response callback {} for request {}. Response was {}", callback, request,
316 // The number of coils/discrete inputs received in response are always in the multiples of 8
318 // So even if querying 5 bits, you will actually get 8 bits. Here we wrap the data in
319 // BitArrayWrappingBitVector
320 // with will validate that the consumer is not accessing the "invalid" bits of the response.
321 int dataItemsInResponse = getNumberOfItemsInResponse(response, request);
322 if (request.getFunctionCode() == ModbusReadFunctionCode.READ_COILS) {
323 BitVector bits = ((ReadCoilsResponse) response).getCoils();
324 BitArray payload = bitArrayFromBitVector(bits, Math.min(dataItemsInResponse, request.getDataLength()));
325 callback.handle(new AsyncModbusReadResult(request, payload));
326 } else if (request.getFunctionCode() == ModbusReadFunctionCode.READ_INPUT_DISCRETES) {
327 BitVector bits = ((ReadInputDiscretesResponse) response).getDiscretes();
328 BitArray payload = bitArrayFromBitVector(bits, Math.min(dataItemsInResponse, request.getDataLength()));
329 callback.handle(new AsyncModbusReadResult(request, payload));
330 } else if (request.getFunctionCode() == ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS) {
331 ModbusRegisterArray payload = modbusRegisterArrayFromInputRegisters(
332 ((ReadMultipleRegistersResponse) response).getRegisters());
333 callback.handle(new AsyncModbusReadResult(request, payload));
334 } else if (request.getFunctionCode() == ModbusReadFunctionCode.READ_INPUT_REGISTERS) {
335 ModbusRegisterArray payload = modbusRegisterArrayFromInputRegisters(
336 ((ReadInputRegistersResponse) response).getRegisters());
337 callback.handle(new AsyncModbusReadResult(request, payload));
339 throw new IllegalArgumentException(
340 String.format("Unexpected function code %s", request.getFunctionCode()));
343 getLogger().trace("Called read response callback {} for request {}. Response was {}", callback, request,