]> git.basschouten.com Git - openhab-addons.git/blob
40fe719c3d45636b26e4a8c7ea3ae304460b919f
[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.internal;
14
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;
19
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;
39
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;
66
67 /**
68  * Conversion utilities between underlying Modbus library (net.wimpi.modbus) and this transport bundle
69  *
70  * @author Sami Salonen - Initial contribution
71  *
72  */
73 @NonNullByDefault
74 public class ModbusLibraryWrapper {
75
76     private static Logger getLogger() {
77         return LoggerFactory.getLogger(ModbusLibraryWrapper.class);
78     }
79
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);
84         }
85         return new BitArray(bits);
86     }
87
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();
92         }
93         return new ModbusRegisterArray(registers);
94     }
95
96     /**
97      * Convert the general request to Modbus library request object
98      *
99      * @param message
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
106      *             bug
107      * @return MODBUS library request matching the write request
108      */
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()) {
114             case WRITE_COIL:
115                 writeSingle.set(true);
116                 // fall-through on purpose
117             case WRITE_MULTIPLE_COILS:
118                 message.accept(new ModbusWriteRequestBlueprintVisitor() {
119
120                     @Override
121                     public void visit(ModbusWriteRegisterRequestBlueprint blueprint) {
122                         throw new IllegalArgumentException();
123                     }
124
125                     @Override
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");
130                         }
131                         if (writeSingle.get()) {
132                             if (coils.size() != 1) {
133                                 throw new IllegalArgumentException("Must provide single coil with WRITE_COIL");
134                             }
135                             request.set(new WriteCoilRequest(message.getReference(), coils.getBit(0)));
136                         } else {
137                             request.set(new WriteMultipleCoilsRequest(message.getReference(),
138                                     ModbusLibraryWrapper.convertBits(coils)));
139                         }
140                     }
141                 });
142                 break;
143             case WRITE_SINGLE_REGISTER:
144                 writeSingle.set(true);
145                 // fall-through on purpose
146             case WRITE_MULTIPLE_REGISTERS:
147                 message.accept(new ModbusWriteRequestBlueprintVisitor() {
148
149                     @Override
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");
154                         }
155                         if (writeSingle.get()) {
156                             if (blueprint.getRegisters().size() != 1) {
157                                 throw new IllegalArgumentException(
158                                         "Must provide single register with WRITE_SINGLE_REGISTER");
159                             }
160                             request.set(new WriteSingleRegisterRequest(message.getReference(), registers[0]));
161                         } else {
162                             request.set(new WriteMultipleRegistersRequest(message.getReference(), registers));
163                         }
164                     }
165
166                     @Override
167                     public void visit(ModbusWriteCoilRequestBlueprint blueprint) {
168                         throw new IllegalArgumentException();
169                     }
170                 });
171                 break;
172             default:
173                 getLogger().error("Unexpected function code {}", message.getFunctionCode());
174                 throw new IllegalStateException(
175                         String.format("Unexpected function code %s", message.getFunctionCode()));
176         }
177         ModbusRequest modbusRequest = request.get();
178         modbusRequest.setUnitID(message.getUnitID());
179         modbusRequest.setProtocolID(message.getProtocolID());
180         return modbusRequest;
181     }
182
183     /**
184      * Create a fresh transaction for the given endpoint and connection
185      *
186      * The retries of the transaction will be disabled.
187      *
188      * @param endpoint
189      * @param connection
190      * @return
191      */
192     public static ModbusTransaction createTransactionForEndpoint(ModbusSlaveEndpoint endpoint,
193             ModbusSlaveConnection connection) {
194         ModbusTransaction transaction = endpoint.accept(new ModbusSlaveEndpointVisitor<ModbusTransaction>() {
195
196             @Override
197             public @NonNull ModbusTransaction visit(ModbusTCPSlaveEndpoint modbusIPSlavePoolingKey) {
198                 ModbusTCPTransaction transaction = new ModbusTCPTransaction();
199                 transaction.setReconnecting(false);
200                 return transaction;
201             }
202
203             @Override
204             public @NonNull ModbusTransaction visit(ModbusSerialSlaveEndpoint modbusSerialSlavePoolingKey) {
205                 return new ModbusSerialTransaction();
206             }
207
208             @Override
209             public @NonNull ModbusTransaction visit(ModbusUDPSlaveEndpoint modbusUDPSlavePoolingKey) {
210                 return new ModbusUDPTransaction();
211             }
212         });
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);
222         } else {
223             throw new IllegalStateException();
224         }
225         return transaction;
226     }
227
228     /**
229      * Create fresh request corresponding to {@link ModbusReadRequestBlueprint}
230      *
231      * @param message
232      * @return
233      */
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());
244         } else {
245             throw new IllegalArgumentException(String.format("Unexpected function code %s", message.getFunctionCode()));
246         }
247         request.setUnitID(message.getUnitID());
248         request.setProtocolID(message.getProtocolID());
249
250         return request;
251     }
252
253     /**
254      * Convert {@link BitArray} to {@link BitVector}
255      *
256      * @param bits
257      * @return
258      */
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)));
262         return bitVector;
263     }
264
265     /**
266      * Convert {@link ModbusRegisterArray} to array of {@link Register}
267      *
268      * @param bits
269      * @return
270      */
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]);
274     }
275
276     /**
277      * Get number of bits/registers/discrete inputs in the request.
278      *
279      *
280      * @param response
281      * @param request
282      * @return
283      */
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.
287         //
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;
298         } else {
299             throw new IllegalArgumentException(String.format("Unexpected function code %s", request.getFunctionCode()));
300         }
301         return responseCount;
302     }
303
304     /**
305      * Invoke callback with the data received
306      *
307      * @param message original request
308      * @param callback callback for read
309      * @param response Modbus library response object
310      */
311     public static void invokeCallbackWithResponse(ModbusReadRequestBlueprint request, ModbusReadCallback callback,
312             ModbusResponse response) {
313         try {
314             getLogger().trace("Calling read response callback {} for request {}. Response was {}", callback, request,
315                     response);
316             // The number of coils/discrete inputs received in response are always in the multiples of 8
317             // bits.
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));
338             } else {
339                 throw new IllegalArgumentException(
340                         String.format("Unexpected function code %s", request.getFunctionCode()));
341             }
342         } finally {
343             getLogger().trace("Called read response callback {} for request {}. Response was {}", callback, request,
344                     response);
345         }
346     }
347 }