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.ModbusRegister;
28 import org.openhab.io.transport.modbus.ModbusRegisterArray;
29 import org.openhab.io.transport.modbus.ModbusWriteCoilRequestBlueprint;
30 import org.openhab.io.transport.modbus.ModbusWriteRegisterRequestBlueprint;
31 import org.openhab.io.transport.modbus.ModbusWriteRequestBlueprint;
32 import org.openhab.io.transport.modbus.ModbusWriteRequestBlueprintVisitor;
33 import org.openhab.io.transport.modbus.endpoint.ModbusSerialSlaveEndpoint;
34 import org.openhab.io.transport.modbus.endpoint.ModbusSlaveEndpoint;
35 import org.openhab.io.transport.modbus.endpoint.ModbusSlaveEndpointVisitor;
36 import org.openhab.io.transport.modbus.endpoint.ModbusTCPSlaveEndpoint;
37 import org.openhab.io.transport.modbus.endpoint.ModbusUDPSlaveEndpoint;
38 import org.slf4j.Logger;
39 import org.slf4j.LoggerFactory;
41 import net.wimpi.modbus.io.ModbusSerialTransaction;
42 import net.wimpi.modbus.io.ModbusTCPTransaction;
43 import net.wimpi.modbus.io.ModbusTransaction;
44 import net.wimpi.modbus.io.ModbusUDPTransaction;
45 import net.wimpi.modbus.msg.ModbusRequest;
46 import net.wimpi.modbus.msg.ModbusResponse;
47 import net.wimpi.modbus.msg.ReadCoilsRequest;
48 import net.wimpi.modbus.msg.ReadCoilsResponse;
49 import net.wimpi.modbus.msg.ReadInputDiscretesRequest;
50 import net.wimpi.modbus.msg.ReadInputDiscretesResponse;
51 import net.wimpi.modbus.msg.ReadInputRegistersRequest;
52 import net.wimpi.modbus.msg.ReadInputRegistersResponse;
53 import net.wimpi.modbus.msg.ReadMultipleRegistersRequest;
54 import net.wimpi.modbus.msg.ReadMultipleRegistersResponse;
55 import net.wimpi.modbus.msg.WriteCoilRequest;
56 import net.wimpi.modbus.msg.WriteMultipleCoilsRequest;
57 import net.wimpi.modbus.msg.WriteMultipleRegistersRequest;
58 import net.wimpi.modbus.msg.WriteSingleRegisterRequest;
59 import net.wimpi.modbus.net.ModbusSlaveConnection;
60 import net.wimpi.modbus.net.SerialConnection;
61 import net.wimpi.modbus.net.TCPMasterConnection;
62 import net.wimpi.modbus.net.UDPMasterConnection;
63 import net.wimpi.modbus.procimg.InputRegister;
64 import net.wimpi.modbus.procimg.Register;
65 import net.wimpi.modbus.procimg.SimpleInputRegister;
66 import net.wimpi.modbus.util.BitVector;
69 * Conversion utilities between underlying Modbus library (net.wimpi.modbus) and this transport bundle
71 * @author Sami Salonen - Initial contribution
75 public class ModbusLibraryWrapper {
77 private static Logger getLogger() {
78 return LoggerFactory.getLogger(ModbusLibraryWrapper.class);
81 private static BitArray bitArrayFromBitVector(BitVector bitVector, int count) {
82 boolean[] bits = new boolean[count];
83 for (int i = 0; i < count; i++) {
84 bits[i] = bitVector.getBit(i);
86 return new BitArray(bits);
89 private static ModbusRegisterArray modbusRegisterArrayFromInputRegisters(InputRegister[] inputRegisters) {
90 ModbusRegister[] registers = new ModbusRegister[inputRegisters.length];
91 for (int i = 0; i < inputRegisters.length; i++) {
92 registers[i] = new ModbusRegister(inputRegisters[i].getValue());
94 return new ModbusRegisterArray(registers);
98 * Convert the general request to Modbus library request object
101 * @throws IllegalArgumentException
102 * 1) in case function code implies coil data but we have registers
103 * 2) in case function code implies register data but we have coils
104 * 3) in case there is no data
105 * 4) in case there is too much data in case of WRITE_COIL or WRITE_SINGLE_REGISTER
106 * @throws IllegalStateException unexpected function code. Implementation is lacking and this can be considered a
108 * @return MODBUS library request matching the write request
110 public static ModbusRequest createRequest(ModbusWriteRequestBlueprint message) {
111 // ModbusRequest[] request = new ModbusRequest[1];
112 AtomicReference<ModbusRequest> request = new AtomicReference<>();
113 AtomicBoolean writeSingle = new AtomicBoolean(false);
114 switch (message.getFunctionCode()) {
116 writeSingle.set(true);
117 // fall-through on purpose
118 case WRITE_MULTIPLE_COILS:
119 message.accept(new ModbusWriteRequestBlueprintVisitor() {
122 public void visit(ModbusWriteRegisterRequestBlueprint blueprint) {
123 throw new IllegalArgumentException();
127 public void visit(ModbusWriteCoilRequestBlueprint blueprint) {
128 BitArray coils = blueprint.getCoils();
129 if (coils.size() == 0) {
130 throw new IllegalArgumentException("Must provide at least one coil");
132 if (writeSingle.get()) {
133 if (coils.size() != 1) {
134 throw new IllegalArgumentException("Must provide single coil with WRITE_COIL");
136 request.set(new WriteCoilRequest(message.getReference(), coils.getBit(0)));
138 request.set(new WriteMultipleCoilsRequest(message.getReference(),
139 ModbusLibraryWrapper.convertBits(coils)));
144 case WRITE_SINGLE_REGISTER:
145 writeSingle.set(true);
146 // fall-through on purpose
147 case WRITE_MULTIPLE_REGISTERS:
148 message.accept(new ModbusWriteRequestBlueprintVisitor() {
151 public void visit(ModbusWriteRegisterRequestBlueprint blueprint) {
152 Register[] registers = ModbusLibraryWrapper.convertRegisters(blueprint.getRegisters());
153 if (registers.length == 0) {
154 throw new IllegalArgumentException("Must provide at least one register");
156 if (writeSingle.get()) {
157 if (blueprint.getRegisters().size() != 1) {
158 throw new IllegalArgumentException(
159 "Must provide single register with WRITE_SINGLE_REGISTER");
161 request.set(new WriteSingleRegisterRequest(message.getReference(), registers[0]));
163 request.set(new WriteMultipleRegistersRequest(message.getReference(), registers));
168 public void visit(ModbusWriteCoilRequestBlueprint blueprint) {
169 throw new IllegalArgumentException();
174 getLogger().error("Unexpected function code {}", message.getFunctionCode());
175 throw new IllegalStateException(
176 String.format("Unexpected function code %s", message.getFunctionCode()));
178 ModbusRequest modbusRequest = request.get();
179 modbusRequest.setUnitID(message.getUnitID());
180 modbusRequest.setProtocolID(message.getProtocolID());
181 return modbusRequest;
185 * Create a fresh transaction for the given endpoint and connection
187 * The retries of the transaction will be disabled.
193 public static ModbusTransaction createTransactionForEndpoint(ModbusSlaveEndpoint endpoint,
194 ModbusSlaveConnection connection) {
195 ModbusTransaction transaction = endpoint.accept(new ModbusSlaveEndpointVisitor<ModbusTransaction>() {
198 public @NonNull ModbusTransaction visit(ModbusTCPSlaveEndpoint modbusIPSlavePoolingKey) {
199 ModbusTCPTransaction transaction = new ModbusTCPTransaction();
200 transaction.setReconnecting(false);
205 public @NonNull ModbusTransaction visit(ModbusSerialSlaveEndpoint modbusSerialSlavePoolingKey) {
206 return new ModbusSerialTransaction();
210 public @NonNull ModbusTransaction visit(ModbusUDPSlaveEndpoint modbusUDPSlavePoolingKey) {
211 return new ModbusUDPTransaction();
214 // We disable modbus library retries and handle in the Manager implementation
215 transaction.setRetries(0);
216 transaction.setRetryDelayMillis(0);
217 if (transaction instanceof ModbusSerialTransaction) {
218 ((ModbusSerialTransaction) transaction).setSerialConnection((SerialConnection) connection);
219 } else if (transaction instanceof ModbusUDPTransaction) {
220 ((ModbusUDPTransaction) transaction).setTerminal(((UDPMasterConnection) connection).getTerminal());
221 } else if (transaction instanceof ModbusTCPTransaction) {
222 ((ModbusTCPTransaction) transaction).setConnection((TCPMasterConnection) connection);
224 throw new IllegalStateException();
230 * Create fresh request corresponding to {@link ModbusReadRequestBlueprint}
235 public static ModbusRequest createRequest(ModbusReadRequestBlueprint message) {
236 ModbusRequest request;
237 if (message.getFunctionCode() == ModbusReadFunctionCode.READ_COILS) {
238 request = new ReadCoilsRequest(message.getReference(), message.getDataLength());
239 } else if (message.getFunctionCode() == ModbusReadFunctionCode.READ_INPUT_DISCRETES) {
240 request = new ReadInputDiscretesRequest(message.getReference(), message.getDataLength());
241 } else if (message.getFunctionCode() == ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS) {
242 request = new ReadMultipleRegistersRequest(message.getReference(), message.getDataLength());
243 } else if (message.getFunctionCode() == ModbusReadFunctionCode.READ_INPUT_REGISTERS) {
244 request = new ReadInputRegistersRequest(message.getReference(), message.getDataLength());
246 throw new IllegalArgumentException(String.format("Unexpected function code %s", message.getFunctionCode()));
248 request.setUnitID(message.getUnitID());
249 request.setProtocolID(message.getProtocolID());
255 * Convert {@link BitArray} to {@link BitVector}
260 public static BitVector convertBits(BitArray bits) {
261 BitVector bitVector = new BitVector(bits.size());
262 IntStream.range(0, bits.size()).forEach(i -> bitVector.setBit(i, bits.getBit(i)));
267 * Convert {@link ModbusRegisterArray} to array of {@link Register}
272 public static Register[] convertRegisters(ModbusRegisterArray arr) {
273 return IntStream.range(0, arr.size()).mapToObj(i -> new SimpleInputRegister(arr.getRegister(i).getValue()))
274 .collect(Collectors.toList()).toArray(new Register[0]);
278 * Get number of bits/registers/discrete inputs in the request.
285 public static int getNumberOfItemsInResponse(ModbusResponse response, ModbusReadRequestBlueprint request) {
286 // jamod library seems to be a bit buggy when it comes number of coils/discrete inputs in the response. Some
287 // of the methods such as ReadCoilsResponse.getBitCount() are returning wrong values.
289 // This is the reason we use a bit more verbose way to get the number of items in the response.
290 final int responseCount;
291 if (request.getFunctionCode() == ModbusReadFunctionCode.READ_COILS) {
292 responseCount = ((ReadCoilsResponse) response).getCoils().size();
293 } else if (request.getFunctionCode() == ModbusReadFunctionCode.READ_INPUT_DISCRETES) {
294 responseCount = ((ReadInputDiscretesResponse) response).getDiscretes().size();
295 } else if (request.getFunctionCode() == ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS) {
296 responseCount = ((ReadMultipleRegistersResponse) response).getRegisters().length;
297 } else if (request.getFunctionCode() == ModbusReadFunctionCode.READ_INPUT_REGISTERS) {
298 responseCount = ((ReadInputRegistersResponse) response).getRegisters().length;
300 throw new IllegalArgumentException(String.format("Unexpected function code %s", request.getFunctionCode()));
302 return responseCount;
306 * Invoke callback with the data received
308 * @param message original request
309 * @param callback callback for read
310 * @param response Modbus library response object
312 public static void invokeCallbackWithResponse(ModbusReadRequestBlueprint request, ModbusReadCallback callback,
313 ModbusResponse response) {
315 getLogger().trace("Calling read response callback {} for request {}. Response was {}", callback, request,
317 // The number of coils/discrete inputs received in response are always in the multiples of 8
319 // So even if querying 5 bits, you will actually get 8 bits. Here we wrap the data in
320 // BitArrayWrappingBitVector
321 // with will validate that the consumer is not accessing the "invalid" bits of the response.
322 int dataItemsInResponse = getNumberOfItemsInResponse(response, request);
323 if (request.getFunctionCode() == ModbusReadFunctionCode.READ_COILS) {
324 BitVector bits = ((ReadCoilsResponse) response).getCoils();
325 BitArray payload = bitArrayFromBitVector(bits, Math.min(dataItemsInResponse, request.getDataLength()));
326 callback.handle(new AsyncModbusReadResult(request, payload));
327 } else if (request.getFunctionCode() == ModbusReadFunctionCode.READ_INPUT_DISCRETES) {
328 BitVector bits = ((ReadInputDiscretesResponse) response).getDiscretes();
329 BitArray payload = bitArrayFromBitVector(bits, Math.min(dataItemsInResponse, request.getDataLength()));
330 callback.handle(new AsyncModbusReadResult(request, payload));
331 } else if (request.getFunctionCode() == ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS) {
332 ModbusRegisterArray payload = modbusRegisterArrayFromInputRegisters(
333 ((ReadMultipleRegistersResponse) response).getRegisters());
334 callback.handle(new AsyncModbusReadResult(request, payload));
335 } else if (request.getFunctionCode() == ModbusReadFunctionCode.READ_INPUT_REGISTERS) {
336 ModbusRegisterArray payload = modbusRegisterArrayFromInputRegisters(
337 ((ReadInputRegistersResponse) response).getRegisters());
338 callback.handle(new AsyncModbusReadResult(request, payload));
340 throw new IllegalArgumentException(
341 String.format("Unexpected function code %s", request.getFunctionCode()));
344 getLogger().trace("Called read response callback {} for request {}. Response was {}", callback, request,