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