2 * Copyright (c) 2010-2021 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.binding.modbus.tests;
15 import static org.hamcrest.CoreMatchers.*;
16 import static org.hamcrest.MatcherAssert.assertThat;
17 import static org.junit.jupiter.api.Assertions.*;
18 import static org.mockito.ArgumentMatchers.*;
19 import static org.mockito.ArgumentMatchers.any;
20 import static org.mockito.Mockito.*;
21 import static org.openhab.binding.modbus.internal.ModbusBindingConstantsInternal.*;
23 import java.util.ArrayList;
24 import java.util.Arrays;
25 import java.util.HashMap;
26 import java.util.List;
28 import java.util.Map.Entry;
29 import java.util.Objects;
30 import java.util.concurrent.ScheduledFuture;
31 import java.util.function.Consumer;
32 import java.util.function.Function;
34 import org.hamcrest.Matcher;
35 import org.junit.jupiter.api.AfterEach;
36 import org.junit.jupiter.api.Test;
37 import org.mockito.Mockito;
38 import org.openhab.binding.modbus.handler.EndpointNotInitializedException;
39 import org.openhab.binding.modbus.handler.ModbusPollerThingHandler;
40 import org.openhab.binding.modbus.internal.handler.ModbusDataThingHandler;
41 import org.openhab.binding.modbus.internal.handler.ModbusTcpThingHandler;
42 import org.openhab.core.config.core.Configuration;
43 import org.openhab.core.io.transport.modbus.AsyncModbusFailure;
44 import org.openhab.core.io.transport.modbus.AsyncModbusReadResult;
45 import org.openhab.core.io.transport.modbus.AsyncModbusWriteResult;
46 import org.openhab.core.io.transport.modbus.BitArray;
47 import org.openhab.core.io.transport.modbus.ModbusConstants;
48 import org.openhab.core.io.transport.modbus.ModbusConstants.ValueType;
49 import org.openhab.core.io.transport.modbus.ModbusReadFunctionCode;
50 import org.openhab.core.io.transport.modbus.ModbusReadRequestBlueprint;
51 import org.openhab.core.io.transport.modbus.ModbusRegisterArray;
52 import org.openhab.core.io.transport.modbus.ModbusResponse;
53 import org.openhab.core.io.transport.modbus.ModbusWriteCoilRequestBlueprint;
54 import org.openhab.core.io.transport.modbus.ModbusWriteFunctionCode;
55 import org.openhab.core.io.transport.modbus.ModbusWriteRegisterRequestBlueprint;
56 import org.openhab.core.io.transport.modbus.ModbusWriteRequestBlueprint;
57 import org.openhab.core.io.transport.modbus.PollTask;
58 import org.openhab.core.io.transport.modbus.endpoint.ModbusSlaveEndpoint;
59 import org.openhab.core.io.transport.modbus.endpoint.ModbusTCPSlaveEndpoint;
60 import org.openhab.core.items.GenericItem;
61 import org.openhab.core.items.Item;
62 import org.openhab.core.library.types.DecimalType;
63 import org.openhab.core.library.types.OnOffType;
64 import org.openhab.core.library.types.OpenClosedType;
65 import org.openhab.core.library.types.StringType;
66 import org.openhab.core.thing.Bridge;
67 import org.openhab.core.thing.ChannelUID;
68 import org.openhab.core.thing.Thing;
69 import org.openhab.core.thing.ThingStatus;
70 import org.openhab.core.thing.ThingStatusDetail;
71 import org.openhab.core.thing.ThingStatusInfo;
72 import org.openhab.core.thing.ThingUID;
73 import org.openhab.core.thing.binding.ThingHandler;
74 import org.openhab.core.thing.binding.builder.BridgeBuilder;
75 import org.openhab.core.thing.binding.builder.ChannelBuilder;
76 import org.openhab.core.thing.binding.builder.ThingBuilder;
77 import org.openhab.core.transform.TransformationException;
78 import org.openhab.core.transform.TransformationService;
79 import org.openhab.core.types.Command;
80 import org.openhab.core.types.RefreshType;
81 import org.openhab.core.types.State;
82 import org.openhab.core.types.UnDefType;
83 import org.osgi.framework.BundleContext;
84 import org.osgi.framework.InvalidSyntaxException;
87 * @author Sami Salonen - Initial contribution
89 public class ModbusDataHandlerTest extends AbstractModbusOSGiTest {
91 private final class MultiplyTransformation implements TransformationService {
93 public String transform(String function, String source) throws TransformationException {
94 return String.valueOf(Integer.parseInt(function) * Integer.parseInt(source));
98 private static final Map<String, String> CHANNEL_TO_ACCEPTED_TYPE = new HashMap<>();
100 CHANNEL_TO_ACCEPTED_TYPE.put(CHANNEL_SWITCH, "Switch");
101 CHANNEL_TO_ACCEPTED_TYPE.put(CHANNEL_CONTACT, "Contact");
102 CHANNEL_TO_ACCEPTED_TYPE.put(CHANNEL_DATETIME, "DateTime");
103 CHANNEL_TO_ACCEPTED_TYPE.put(CHANNEL_DIMMER, "Dimmer");
104 CHANNEL_TO_ACCEPTED_TYPE.put(CHANNEL_NUMBER, "Number");
105 CHANNEL_TO_ACCEPTED_TYPE.put(CHANNEL_STRING, "String");
106 CHANNEL_TO_ACCEPTED_TYPE.put(CHANNEL_ROLLERSHUTTER, "Rollershutter");
107 CHANNEL_TO_ACCEPTED_TYPE.put(CHANNEL_LAST_READ_SUCCESS, "DateTime");
108 CHANNEL_TO_ACCEPTED_TYPE.put(CHANNEL_LAST_WRITE_SUCCESS, "DateTime");
109 CHANNEL_TO_ACCEPTED_TYPE.put(CHANNEL_LAST_WRITE_ERROR, "DateTime");
110 CHANNEL_TO_ACCEPTED_TYPE.put(CHANNEL_LAST_READ_ERROR, "DateTime");
112 private List<ModbusWriteRequestBlueprint> writeRequests = new ArrayList<>();
115 public void tearDown() {
116 writeRequests.clear();
119 private void captureModbusWrites() {
120 Mockito.when(comms.submitOneTimeWrite(any(), any(), any())).then(invocation -> {
121 ModbusWriteRequestBlueprint task = (ModbusWriteRequestBlueprint) invocation.getArgument(0);
122 writeRequests.add(task);
123 return Mockito.mock(ScheduledFuture.class);
127 private Bridge createPollerMock(String pollerId, PollTask task) {
129 ThingUID thingUID = new ThingUID(THING_TYPE_MODBUS_POLLER, pollerId);
130 BridgeBuilder builder = BridgeBuilder.create(THING_TYPE_MODBUS_POLLER, thingUID)
131 .withLabel("label for " + pollerId);
132 for (Entry<String, String> entry : CHANNEL_TO_ACCEPTED_TYPE.entrySet()) {
133 String channelId = entry.getKey();
134 String channelAcceptedType = entry.getValue();
135 builder = builder.withChannel(
136 ChannelBuilder.create(new ChannelUID(thingUID, channelId), channelAcceptedType).build());
138 poller = builder.build();
139 poller.setStatusInfo(new ThingStatusInfo(ThingStatus.ONLINE, ThingStatusDetail.NONE, ""));
141 ModbusPollerThingHandler mockHandler = Mockito.mock(ModbusPollerThingHandler.class);
142 doReturn(task.getRequest()).when(mockHandler).getRequest();
143 assert comms != null;
144 doReturn(comms).when(mockHandler).getCommunicationInterface();
145 doReturn(task.getEndpoint()).when(comms).getEndpoint();
146 poller.setHandler(mockHandler);
147 assertSame(poller.getHandler(), mockHandler);
148 assertSame(((ModbusPollerThingHandler) poller.getHandler()).getCommunicationInterface().getEndpoint(),
150 assertSame(((ModbusPollerThingHandler) poller.getHandler()).getRequest(), task.getRequest());
156 private Bridge createTcpMock() {
157 ModbusSlaveEndpoint endpoint = new ModbusTCPSlaveEndpoint("thisishost", 502, false);
158 Bridge tcpBridge = ModbusPollerThingHandlerTest.createTcpThingBuilder("tcp1").build();
159 ModbusTcpThingHandler tcpThingHandler = Mockito.mock(ModbusTcpThingHandler.class);
160 tcpBridge.setStatusInfo(new ThingStatusInfo(ThingStatus.ONLINE, ThingStatusDetail.NONE, ""));
161 tcpBridge.setHandler(tcpThingHandler);
162 doReturn(comms).when(tcpThingHandler).getCommunicationInterface();
164 doReturn(0).when(tcpThingHandler).getSlaveId();
165 } catch (EndpointNotInitializedException e) {
166 // not raised -- we are mocking return value only, not actually calling the method
167 throw new IllegalStateException();
169 tcpThingHandler.initialize();
170 assertThat(tcpBridge.getStatus(), is(equalTo(ThingStatus.ONLINE)));
174 private ModbusDataThingHandler createDataHandler(String id, Bridge bridge,
175 Function<ThingBuilder, ThingBuilder> builderConfigurator) {
176 return createDataHandler(id, bridge, builderConfigurator, null);
179 private ModbusDataThingHandler createDataHandler(String id, Bridge bridge,
180 Function<ThingBuilder, ThingBuilder> builderConfigurator, BundleContext context) {
181 return createDataHandler(id, bridge, builderConfigurator, context, true);
184 private ModbusDataThingHandler createDataHandler(String id, Bridge bridge,
185 Function<ThingBuilder, ThingBuilder> builderConfigurator, BundleContext context,
186 boolean autoCreateItemsAndLinkToChannels) {
187 ThingUID thingUID = new ThingUID(THING_TYPE_MODBUS_DATA, id);
188 ThingBuilder builder = ThingBuilder.create(THING_TYPE_MODBUS_DATA, thingUID).withLabel("label for " + id);
189 Map<String, ChannelUID> toBeLinked = new HashMap<>();
190 for (Entry<String, String> entry : CHANNEL_TO_ACCEPTED_TYPE.entrySet()) {
191 String channelId = entry.getKey();
192 // accepted item type
193 String channelAcceptedType = entry.getValue();
194 ChannelUID channelUID = new ChannelUID(thingUID, channelId);
195 builder = builder.withChannel(ChannelBuilder.create(channelUID, channelAcceptedType).build());
197 if (autoCreateItemsAndLinkToChannels) {
198 // Create item of correct type and link it to channel
199 String itemName = getItemName(channelUID);
200 final GenericItem item;
201 item = coreItemFactory.createItem(channelAcceptedType, itemName);
202 assertThat(String.format("Could not determine correct item type for %s", channelId), item,
205 Objects.requireNonNull(item);
207 toBeLinked.put(itemName, channelUID);
210 if (builderConfigurator != null) {
211 builder = builderConfigurator.apply(builder);
214 Thing dataThing = builder.withBridge(bridge.getUID()).build();
217 // Link after the things and items have been created
218 for (Entry<String, ChannelUID> entry : toBeLinked.entrySet()) {
219 linkItem(entry.getKey(), entry.getValue());
221 return (ModbusDataThingHandler) dataThing.getHandler();
224 private String getItemName(ChannelUID channelUID) {
225 return channelUID.toString().replace(':', '_') + "_item";
228 private void assertSingleStateUpdate(ModbusDataThingHandler handler, String channel, Matcher<State> matcher) {
229 waitForAssert(() -> {
230 ChannelUID channelUID = new ChannelUID(handler.getThing().getUID(), channel);
231 String itemName = getItemName(channelUID);
232 Item item = itemRegistry.get(itemName);
233 assertThat(String.format("Item %s is not available from item registry", itemName), item,
236 List<State> updates = getStateUpdates(itemName);
237 if (updates != null) {
239 String.format("Many updates found, expected one: %s", Arrays.deepToString(updates.toArray())),
240 updates.size(), is(equalTo(1)));
242 State state = updates == null ? null : updates.get(0);
243 assertThat(String.format("%s %s, state %s of type %s", item.getClass().getSimpleName(), itemName, state,
244 state == null ? null : state.getClass().getSimpleName()), state, is(matcher));
248 private void assertSingleStateUpdate(ModbusDataThingHandler handler, String channel, State state) {
249 assertSingleStateUpdate(handler, channel, is(equalTo(state)));
252 private void testOutOfBoundsGeneric(int pollStart, int pollLength, String start,
253 ModbusReadFunctionCode functionCode, ValueType valueType, ThingStatus expectedStatus) {
254 testOutOfBoundsGeneric(pollStart, pollLength, start, functionCode, valueType, expectedStatus, null);
257 private void testOutOfBoundsGeneric(int pollStart, int pollLength, String start,
258 ModbusReadFunctionCode functionCode, ValueType valueType, ThingStatus expectedStatus,
259 BundleContext context) {
260 ModbusSlaveEndpoint endpoint = new ModbusTCPSlaveEndpoint("thisishost", 502, false);
262 // Minimally mocked request
263 ModbusReadRequestBlueprint request = Mockito.mock(ModbusReadRequestBlueprint.class);
264 doReturn(pollStart).when(request).getReference();
265 doReturn(pollLength).when(request).getDataLength();
266 doReturn(functionCode).when(request).getFunctionCode();
268 PollTask task = Mockito.mock(PollTask.class);
269 doReturn(endpoint).when(task).getEndpoint();
270 doReturn(request).when(task).getRequest();
272 Bridge pollerThing = createPollerMock("poller1", task);
274 Configuration dataConfig = new Configuration();
275 dataConfig.put("readStart", start);
276 dataConfig.put("readTransform", "default");
277 dataConfig.put("readValueType", valueType.getConfigValue());
278 ModbusDataThingHandler dataHandler = createDataHandler("data1", pollerThing,
279 builder -> builder.withConfiguration(dataConfig), context);
280 assertThat(dataHandler.getThing().getStatusInfo().getDescription(), dataHandler.getThing().getStatus(),
281 is(equalTo(expectedStatus)));
285 public void testInitCoilsOutOfIndex() {
286 testOutOfBoundsGeneric(4, 3, "8", ModbusReadFunctionCode.READ_COILS, ModbusConstants.ValueType.BIT,
287 ThingStatus.OFFLINE);
291 public void testInitCoilsOutOfIndex2() {
292 // Reading coils 4, 5, 6. Coil 7 is out of bounds
293 testOutOfBoundsGeneric(4, 3, "7", ModbusReadFunctionCode.READ_COILS, ModbusConstants.ValueType.BIT,
294 ThingStatus.OFFLINE);
298 public void testInitCoilsOK() {
299 // Reading coils 4, 5, 6. Coil 6 is OK
300 testOutOfBoundsGeneric(4, 3, "6", ModbusReadFunctionCode.READ_COILS, ModbusConstants.ValueType.BIT,
305 public void testInitRegistersWithBitOutOfIndex() {
306 testOutOfBoundsGeneric(4, 3, "8.0", ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS,
307 ModbusConstants.ValueType.BIT, ThingStatus.OFFLINE);
311 public void testInitRegistersWithBitOutOfIndex2() {
312 testOutOfBoundsGeneric(4, 3, "7.16", ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS,
313 ModbusConstants.ValueType.BIT, ThingStatus.OFFLINE);
317 public void testInitRegistersWithBitOK() {
318 testOutOfBoundsGeneric(4, 3, "6.0", ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS,
319 ModbusConstants.ValueType.BIT, ThingStatus.ONLINE);
323 public void testInitRegistersWithBitOK2() {
324 testOutOfBoundsGeneric(4, 3, "6.15", ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS,
325 ModbusConstants.ValueType.BIT, ThingStatus.ONLINE);
329 public void testInitRegistersWithInt8OutOfIndex() {
330 testOutOfBoundsGeneric(4, 3, "8.0", ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS,
331 ModbusConstants.ValueType.INT8, ThingStatus.OFFLINE);
335 public void testInitRegistersWithInt8OutOfIndex2() {
336 testOutOfBoundsGeneric(4, 3, "7.2", ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS,
337 ModbusConstants.ValueType.INT8, ThingStatus.OFFLINE);
341 public void testInitRegistersWithInt8OK() {
342 testOutOfBoundsGeneric(4, 3, "6.0", ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS,
343 ModbusConstants.ValueType.INT8, ThingStatus.ONLINE);
347 public void testInitRegistersWithInt8OK2() {
348 testOutOfBoundsGeneric(4, 3, "6.1", ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS,
349 ModbusConstants.ValueType.INT8, ThingStatus.ONLINE);
353 public void testInitRegistersWithInt16OK() {
354 // Poller reading registers 4, 5, 6. Register 6 is OK
355 testOutOfBoundsGeneric(4, 3, "6", ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS,
356 ModbusConstants.ValueType.INT16, ThingStatus.ONLINE);
360 public void testInitRegistersWithInt16OutOfBounds() {
361 // Poller reading registers 4, 5, 6. Register 7 is out-of-bounds
362 testOutOfBoundsGeneric(4, 3, "7", ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS,
363 ModbusConstants.ValueType.INT16, ThingStatus.OFFLINE);
367 public void testInitRegistersWithInt16OutOfBounds2() {
368 testOutOfBoundsGeneric(4, 3, "8", ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS,
369 ModbusConstants.ValueType.INT16, ThingStatus.OFFLINE);
373 public void testInitRegistersWithInt16NoDecimalFormatAllowed() {
374 testOutOfBoundsGeneric(4, 3, "7.0", ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS,
375 ModbusConstants.ValueType.INT16, ThingStatus.OFFLINE);
379 public void testInitRegistersWithInt32OK() {
380 testOutOfBoundsGeneric(4, 3, "5", ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS,
381 ModbusConstants.ValueType.INT32, ThingStatus.ONLINE);
385 public void testInitRegistersWithInt32OutOfBounds() {
386 testOutOfBoundsGeneric(4, 3, "6", ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS,
387 ModbusConstants.ValueType.INT32, ThingStatus.OFFLINE);
391 public void testInitRegistersWithInt32AtTheEdge() {
392 testOutOfBoundsGeneric(4, 3, "5", ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS,
393 ModbusConstants.ValueType.INT32, ThingStatus.ONLINE);
396 private ModbusDataThingHandler testReadHandlingGeneric(ModbusReadFunctionCode functionCode, String start,
397 String transform, ValueType valueType, BitArray bits, ModbusRegisterArray registers, Exception error) {
398 return testReadHandlingGeneric(functionCode, start, transform, valueType, bits, registers, error, null);
401 private ModbusDataThingHandler testReadHandlingGeneric(ModbusReadFunctionCode functionCode, String start,
402 String transform, ValueType valueType, BitArray bits, ModbusRegisterArray registers, Exception error,
403 BundleContext context) {
404 return testReadHandlingGeneric(functionCode, start, transform, valueType, bits, registers, error, context,
408 @SuppressWarnings({ "null" })
409 private ModbusDataThingHandler testReadHandlingGeneric(ModbusReadFunctionCode functionCode, String start,
410 String transform, ValueType valueType, BitArray bits, ModbusRegisterArray registers, Exception error,
411 BundleContext context, boolean autoCreateItemsAndLinkToChannels) {
412 ModbusSlaveEndpoint endpoint = new ModbusTCPSlaveEndpoint("thisishost", 502, false);
416 // Minimally mocked request
417 ModbusReadRequestBlueprint request = Mockito.mock(ModbusReadRequestBlueprint.class);
418 doReturn(pollLength).when(request).getDataLength();
419 doReturn(functionCode).when(request).getFunctionCode();
421 PollTask task = Mockito.mock(PollTask.class);
422 doReturn(endpoint).when(task).getEndpoint();
423 doReturn(request).when(task).getRequest();
425 Bridge poller = createPollerMock("poller1", task);
427 Configuration dataConfig = new Configuration();
428 dataConfig.put("readStart", start);
429 dataConfig.put("readTransform", transform);
430 dataConfig.put("readValueType", valueType.getConfigValue());
432 String thingId = "read1";
433 ModbusDataThingHandler dataHandler = createDataHandler(thingId, poller,
434 builder -> builder.withConfiguration(dataConfig), context, autoCreateItemsAndLinkToChannels);
436 assertThat(dataHandler.getThing().getStatus(), is(equalTo(ThingStatus.ONLINE)));
440 assertNull(registers);
442 AsyncModbusReadResult result = new AsyncModbusReadResult(request, bits);
443 dataHandler.onReadResult(result);
444 } else if (registers != null) {
447 AsyncModbusReadResult result = new AsyncModbusReadResult(request, registers);
448 dataHandler.onReadResult(result);
451 assertNull(registers);
452 assertNotNull(error);
453 AsyncModbusFailure<ModbusReadRequestBlueprint> result = new AsyncModbusFailure<ModbusReadRequestBlueprint>(
455 dataHandler.handleReadError(result);
460 @SuppressWarnings({ "null" })
461 private ModbusDataThingHandler testWriteHandlingGeneric(String start, String transform, ValueType valueType,
462 String writeType, ModbusWriteFunctionCode successFC, String channel, Command command, Exception error,
463 BundleContext context) {
464 ModbusSlaveEndpoint endpoint = new ModbusTCPSlaveEndpoint("thisishost", 502, false);
466 // Minimally mocked request
467 ModbusReadRequestBlueprint request = Mockito.mock(ModbusReadRequestBlueprint.class);
469 PollTask task = Mockito.mock(PollTask.class);
470 doReturn(endpoint).when(task).getEndpoint();
471 doReturn(request).when(task).getRequest();
473 Bridge poller = createPollerMock("poller1", task);
475 Configuration dataConfig = new Configuration();
476 dataConfig.put("readStart", "");
477 dataConfig.put("writeStart", start);
478 dataConfig.put("writeTransform", transform);
479 dataConfig.put("writeValueType", valueType.getConfigValue());
480 dataConfig.put("writeType", writeType);
482 String thingId = "write";
484 ModbusDataThingHandler dataHandler = createDataHandler(thingId, poller,
485 builder -> builder.withConfiguration(dataConfig), context);
487 assertThat(dataHandler.getThing().getStatus(), is(equalTo(ThingStatus.ONLINE)));
489 dataHandler.handleCommand(new ChannelUID(dataHandler.getThing().getUID(), channel), command);
492 dataHandler.handleReadError(new AsyncModbusFailure<ModbusReadRequestBlueprint>(request, error));
494 ModbusResponse resp = new ModbusResponse() {
497 public int getFunctionCode() {
498 return successFC.getFunctionCode();
502 .onWriteResponse(new AsyncModbusWriteResult(Mockito.mock(ModbusWriteRequestBlueprint.class), resp));
508 public void testOnError() {
509 ModbusDataThingHandler dataHandler = testReadHandlingGeneric(ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS,
510 "0.0", "default", ModbusConstants.ValueType.BIT, null, null, new Exception("fooerror"));
512 assertSingleStateUpdate(dataHandler, CHANNEL_LAST_READ_ERROR, is(notNullValue(State.class)));
516 public void testOnRegistersInt16StaticTransformation() {
517 ModbusDataThingHandler dataHandler = testReadHandlingGeneric(ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS,
518 "0", "-3", ModbusConstants.ValueType.INT16, null,
519 new ModbusRegisterArray(new byte[] { (byte) 0xff, (byte) 0xfd }), null);
521 assertSingleStateUpdate(dataHandler, CHANNEL_LAST_READ_SUCCESS, is(notNullValue(State.class)));
522 assertSingleStateUpdate(dataHandler, CHANNEL_LAST_READ_ERROR, is(nullValue(State.class)));
524 // -3 converts to "true"
525 assertSingleStateUpdate(dataHandler, CHANNEL_CONTACT, is(nullValue(State.class)));
526 assertSingleStateUpdate(dataHandler, CHANNEL_SWITCH, is(nullValue(State.class)));
527 assertSingleStateUpdate(dataHandler, CHANNEL_DIMMER, is(nullValue(State.class)));
528 assertSingleStateUpdate(dataHandler, CHANNEL_NUMBER, new DecimalType(-3));
529 // roller shutter fails since -3 is invalid value (not between 0...100)
530 // assertThatStateContains(state, CHANNEL_ROLLERSHUTTER, new PercentType(1));
531 assertSingleStateUpdate(dataHandler, CHANNEL_STRING, new StringType("-3"));
532 // no datetime, conversion not possible without transformation
536 public void testOnRegistersRealTransformation() {
537 mockTransformation("MULTIPLY", new MultiplyTransformation());
538 ModbusDataThingHandler dataHandler = testReadHandlingGeneric(ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS,
539 "0", "MULTIPLY(10)", ModbusConstants.ValueType.INT16, null,
540 new ModbusRegisterArray(new byte[] { (byte) 0xff, (byte) 0xfd }), null, bundleContext);
542 assertSingleStateUpdate(dataHandler, CHANNEL_LAST_READ_SUCCESS, is(notNullValue(State.class)));
543 assertSingleStateUpdate(dataHandler, CHANNEL_LAST_READ_ERROR, is(nullValue(State.class)));
545 // transformation output (-30) is not valid for contact or switch
546 assertSingleStateUpdate(dataHandler, CHANNEL_CONTACT, is(nullValue(State.class)));
547 assertSingleStateUpdate(dataHandler, CHANNEL_SWITCH, is(nullValue(State.class)));
548 // -30 is not valid value for Dimmer (PercentType) (not between 0...100)
549 assertSingleStateUpdate(dataHandler, CHANNEL_DIMMER, is(nullValue(State.class)));
550 assertSingleStateUpdate(dataHandler, CHANNEL_NUMBER, new DecimalType(-30));
551 // roller shutter fails since -3 is invalid value (not between 0...100)
552 assertSingleStateUpdate(dataHandler, CHANNEL_ROLLERSHUTTER, is(nullValue(State.class)));
553 assertSingleStateUpdate(dataHandler, CHANNEL_STRING, new StringType("-30"));
554 // no datetime, conversion not possible without transformation
558 public void testOnRegistersNaNFloatInRegisters() throws InvalidSyntaxException {
559 ModbusDataThingHandler dataHandler = testReadHandlingGeneric(ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS,
560 "0", "default", ModbusConstants.ValueType.FLOAT32, null, new ModbusRegisterArray(
561 // equivalent of floating point NaN
562 new byte[] { (byte) 0x7f, (byte) 0xc0, (byte) 0x00, (byte) 0x00 }),
563 null, bundleContext);
565 assertSingleStateUpdate(dataHandler, CHANNEL_LAST_READ_SUCCESS, is(notNullValue(State.class)));
566 assertSingleStateUpdate(dataHandler, CHANNEL_LAST_READ_ERROR, is(nullValue(State.class)));
568 // UNDEF is treated as "boolean true" (OPEN/ON) since it is != 0.
569 assertSingleStateUpdate(dataHandler, CHANNEL_CONTACT, OpenClosedType.OPEN);
570 assertSingleStateUpdate(dataHandler, CHANNEL_SWITCH, OnOffType.ON);
571 assertSingleStateUpdate(dataHandler, CHANNEL_DIMMER, OnOffType.ON);
572 assertSingleStateUpdate(dataHandler, CHANNEL_NUMBER, UnDefType.UNDEF);
573 assertSingleStateUpdate(dataHandler, CHANNEL_ROLLERSHUTTER, UnDefType.UNDEF);
574 assertSingleStateUpdate(dataHandler, CHANNEL_STRING, UnDefType.UNDEF);
578 public void testOnRegistersRealTransformation2() throws InvalidSyntaxException {
579 mockTransformation("ONOFF", new TransformationService() {
582 public String transform(String function, String source) throws TransformationException {
583 return Integer.parseInt(source) != 0 ? "ON" : "OFF";
586 ModbusDataThingHandler dataHandler = testReadHandlingGeneric(ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS,
587 "0", "ONOFF(10)", ModbusConstants.ValueType.INT16, null,
588 new ModbusRegisterArray(new byte[] { (byte) 0xff, (byte) 0xfd }), null, bundleContext);
590 assertSingleStateUpdate(dataHandler, CHANNEL_LAST_READ_SUCCESS, is(notNullValue(State.class)));
591 assertSingleStateUpdate(dataHandler, CHANNEL_LAST_READ_ERROR, is(nullValue(State.class)));
593 assertSingleStateUpdate(dataHandler, CHANNEL_CONTACT, is(nullValue(State.class)));
594 assertSingleStateUpdate(dataHandler, CHANNEL_SWITCH, is(equalTo(OnOffType.ON)));
595 assertSingleStateUpdate(dataHandler, CHANNEL_DIMMER, is(equalTo(OnOffType.ON)));
596 assertSingleStateUpdate(dataHandler, CHANNEL_NUMBER, is(nullValue(State.class)));
597 assertSingleStateUpdate(dataHandler, CHANNEL_ROLLERSHUTTER, is(nullValue(State.class)));
598 assertSingleStateUpdate(dataHandler, CHANNEL_STRING, is(equalTo(new StringType("ON"))));
602 public void testWriteRealTransformation() throws InvalidSyntaxException {
603 captureModbusWrites();
604 mockTransformation("MULTIPLY", new MultiplyTransformation());
605 ModbusDataThingHandler dataHandler = testWriteHandlingGeneric("50", "MULTIPLY(10)",
606 ModbusConstants.ValueType.BIT, "coil", ModbusWriteFunctionCode.WRITE_COIL, "number",
607 new DecimalType("2"), null, bundleContext);
609 assertSingleStateUpdate(dataHandler, CHANNEL_LAST_WRITE_SUCCESS, is(notNullValue(State.class)));
610 assertSingleStateUpdate(dataHandler, CHANNEL_LAST_WRITE_ERROR, is(nullValue(State.class)));
611 assertThat(writeRequests.size(), is(equalTo(1)));
612 ModbusWriteRequestBlueprint writeRequest = writeRequests.get(0);
613 assertThat(writeRequest.getFunctionCode(), is(equalTo(ModbusWriteFunctionCode.WRITE_COIL)));
614 assertThat(writeRequest.getReference(), is(equalTo(50)));
615 assertThat(((ModbusWriteCoilRequestBlueprint) writeRequest).getCoils().size(), is(equalTo(1)));
616 // Since transform output is non-zero, it is mapped as "true"
617 assertThat(((ModbusWriteCoilRequestBlueprint) writeRequest).getCoils().getBit(0), is(equalTo(true)));
621 public void testWriteRealTransformation2() throws InvalidSyntaxException {
622 captureModbusWrites();
623 mockTransformation("ZERO", new TransformationService() {
626 public String transform(String function, String source) throws TransformationException {
630 ModbusDataThingHandler dataHandler = testWriteHandlingGeneric("50", "ZERO(foobar)",
631 ModbusConstants.ValueType.BIT, "coil", ModbusWriteFunctionCode.WRITE_COIL, "number",
632 new DecimalType("2"), null, bundleContext);
634 assertSingleStateUpdate(dataHandler, CHANNEL_LAST_WRITE_SUCCESS, is(notNullValue(State.class)));
635 assertSingleStateUpdate(dataHandler, CHANNEL_LAST_WRITE_ERROR, is(nullValue(State.class)));
636 assertThat(writeRequests.size(), is(equalTo(1)));
637 ModbusWriteRequestBlueprint writeRequest = writeRequests.get(0);
638 assertThat(writeRequest.getFunctionCode(), is(equalTo(ModbusWriteFunctionCode.WRITE_COIL)));
639 assertThat(writeRequest.getReference(), is(equalTo(50)));
640 assertThat(((ModbusWriteCoilRequestBlueprint) writeRequest).getCoils().size(), is(equalTo(1)));
641 // Since transform output is zero, it is mapped as "false"
642 assertThat(((ModbusWriteCoilRequestBlueprint) writeRequest).getCoils().getBit(0), is(equalTo(false)));
646 public void testWriteRealTransformation3() throws InvalidSyntaxException {
647 captureModbusWrites();
648 mockTransformation("RANDOM", new TransformationService() {
651 public String transform(String function, String source) throws TransformationException {
655 ModbusDataThingHandler dataHandler = testWriteHandlingGeneric("50", "RANDOM(foobar)",
656 ModbusConstants.ValueType.INT16, "holding", ModbusWriteFunctionCode.WRITE_SINGLE_REGISTER, "number",
657 new DecimalType("2"), null, bundleContext);
659 assertSingleStateUpdate(dataHandler, CHANNEL_LAST_WRITE_SUCCESS, is(notNullValue(State.class)));
660 assertSingleStateUpdate(dataHandler, CHANNEL_LAST_WRITE_ERROR, is(nullValue(State.class)));
661 assertThat(writeRequests.size(), is(equalTo(1)));
662 ModbusWriteRequestBlueprint writeRequest = writeRequests.get(0);
663 assertThat(writeRequest.getFunctionCode(), is(equalTo(ModbusWriteFunctionCode.WRITE_SINGLE_REGISTER)));
664 assertThat(writeRequest.getReference(), is(equalTo(50)));
665 assertThat(((ModbusWriteRegisterRequestBlueprint) writeRequest).getRegisters().size(), is(equalTo(1)));
666 assertThat(((ModbusWriteRegisterRequestBlueprint) writeRequest).getRegisters().getRegister(0), is(equalTo(5)));
670 public void testWriteRealTransformation4() throws InvalidSyntaxException {
671 captureModbusWrites();
672 mockTransformation("JSON", new TransformationService() {
675 public String transform(String function, String source) throws TransformationException {
677 + "\"functionCode\": 16,"//
678 + "\"address\": 5412,"//
679 + "\"value\": [1, 0, 5]"//
682 + "\"functionCode\": 6,"//
683 + "\"address\": 555,"//
688 ModbusDataThingHandler dataHandler = testWriteHandlingGeneric("50", "JSON(foobar)",
689 ModbusConstants.ValueType.INT16, "holding", ModbusWriteFunctionCode.WRITE_MULTIPLE_REGISTERS, "number",
690 new DecimalType("2"), null, bundleContext);
692 assertSingleStateUpdate(dataHandler, CHANNEL_LAST_WRITE_SUCCESS, is(notNullValue(State.class)));
693 assertSingleStateUpdate(dataHandler, CHANNEL_LAST_WRITE_ERROR, is(nullValue(State.class)));
694 assertThat(writeRequests.size(), is(equalTo(2)));
696 ModbusWriteRequestBlueprint writeRequest = writeRequests.get(0);
697 assertThat(writeRequest.getFunctionCode(), is(equalTo(ModbusWriteFunctionCode.WRITE_MULTIPLE_REGISTERS)));
698 assertThat(writeRequest.getReference(), is(equalTo(5412)));
699 assertThat(((ModbusWriteRegisterRequestBlueprint) writeRequest).getRegisters().size(), is(equalTo(3)));
700 assertThat(((ModbusWriteRegisterRequestBlueprint) writeRequest).getRegisters().getRegister(0),
702 assertThat(((ModbusWriteRegisterRequestBlueprint) writeRequest).getRegisters().getRegister(1),
704 assertThat(((ModbusWriteRegisterRequestBlueprint) writeRequest).getRegisters().getRegister(2),
708 ModbusWriteRequestBlueprint writeRequest = writeRequests.get(1);
709 assertThat(writeRequest.getFunctionCode(), is(equalTo(ModbusWriteFunctionCode.WRITE_SINGLE_REGISTER)));
710 assertThat(writeRequest.getReference(), is(equalTo(555)));
711 assertThat(((ModbusWriteRegisterRequestBlueprint) writeRequest).getRegisters().size(), is(equalTo(1)));
712 assertThat(((ModbusWriteRegisterRequestBlueprint) writeRequest).getRegisters().getRegister(0),
717 private void testValueTypeGeneric(ModbusReadFunctionCode functionCode, ValueType valueType,
718 ThingStatus expectedStatus) {
719 ModbusSlaveEndpoint endpoint = new ModbusTCPSlaveEndpoint("thisishost", 502, false);
721 // Minimally mocked request
722 ModbusReadRequestBlueprint request = Mockito.mock(ModbusReadRequestBlueprint.class);
723 doReturn(3).when(request).getDataLength();
724 doReturn(functionCode).when(request).getFunctionCode();
726 PollTask task = Mockito.mock(PollTask.class);
727 doReturn(endpoint).when(task).getEndpoint();
728 doReturn(request).when(task).getRequest();
730 Bridge poller = createPollerMock("poller1", task);
732 Configuration dataConfig = new Configuration();
733 dataConfig.put("readStart", "1");
734 dataConfig.put("readTransform", "default");
735 dataConfig.put("readValueType", valueType.getConfigValue());
736 ModbusDataThingHandler dataHandler = createDataHandler("data1", poller,
737 builder -> builder.withConfiguration(dataConfig));
738 assertThat(dataHandler.getThing().getStatus(), is(equalTo(expectedStatus)));
742 public void testCoilDoesNotAcceptFloat32ValueType() {
743 testValueTypeGeneric(ModbusReadFunctionCode.READ_COILS, ModbusConstants.ValueType.FLOAT32, ThingStatus.OFFLINE);
747 public void testCoilAcceptsBitValueType() {
748 testValueTypeGeneric(ModbusReadFunctionCode.READ_COILS, ModbusConstants.ValueType.BIT, ThingStatus.ONLINE);
752 public void testDiscreteInputDoesNotAcceptFloat32ValueType() {
753 testValueTypeGeneric(ModbusReadFunctionCode.READ_INPUT_DISCRETES, ModbusConstants.ValueType.FLOAT32,
754 ThingStatus.OFFLINE);
758 public void testDiscreteInputAcceptsBitValueType() {
759 testValueTypeGeneric(ModbusReadFunctionCode.READ_INPUT_DISCRETES, ModbusConstants.ValueType.BIT,
764 public void testRefreshOnData() throws InterruptedException {
765 ModbusReadFunctionCode functionCode = ModbusReadFunctionCode.READ_COILS;
767 ModbusSlaveEndpoint endpoint = new ModbusTCPSlaveEndpoint("thisishost", 502, false);
771 // Minimally mocked request
772 ModbusReadRequestBlueprint request = Mockito.mock(ModbusReadRequestBlueprint.class);
773 doReturn(pollLength).when(request).getDataLength();
774 doReturn(functionCode).when(request).getFunctionCode();
776 PollTask task = Mockito.mock(PollTask.class);
777 doReturn(endpoint).when(task).getEndpoint();
778 doReturn(request).when(task).getRequest();
780 Bridge poller = createPollerMock("poller1", task);
782 Configuration dataConfig = new Configuration();
783 dataConfig.put("readStart", "0");
784 dataConfig.put("readTransform", "default");
785 dataConfig.put("readValueType", "bit");
787 String thingId = "read1";
789 ModbusDataThingHandler dataHandler = createDataHandler(thingId, poller,
790 builder -> builder.withConfiguration(dataConfig), bundleContext);
791 assertThat(dataHandler.getThing().getStatus(), is(equalTo(ThingStatus.ONLINE)));
793 verify(comms, never()).submitOneTimePoll(eq(request), notNull(), notNull());
794 ModbusPollerThingHandler handler = (ModbusPollerThingHandler) poller.getHandler();
795 // Wait for all channels to receive the REFRESH command (initiated by the core)
797 () -> verify((ModbusPollerThingHandler) poller.getHandler(), times(CHANNEL_TO_ACCEPTED_TYPE.size()))
800 reset(poller.getHandler());
802 // Issue REFRESH command and verify the results
803 dataHandler.handleCommand(Mockito.mock(ChannelUID.class), RefreshType.REFRESH);
805 // data handler asynchronously calls the poller.refresh() -- it might take some time
806 // We check that refresh is finally called
807 waitForAssert(() -> verify((ModbusPollerThingHandler) poller.getHandler()).refresh());
812 * @param pollerFunctionCode poller function code. Use null if you want to have data thing direct child of endpoint
814 * @param config thing config
815 * @param statusConsumer assertion method for data thingstatus
817 private void testInitGeneric(ModbusReadFunctionCode pollerFunctionCode, Configuration config,
818 Consumer<ThingStatusInfo> statusConsumer) {
822 if (pollerFunctionCode == null) {
823 parent = createTcpMock();
824 ThingHandler foo = parent.getHandler();
827 ModbusSlaveEndpoint endpoint = new ModbusTCPSlaveEndpoint("thisishost", 502, false);
829 // Minimally mocked request
830 ModbusReadRequestBlueprint request = Mockito.mock(ModbusReadRequestBlueprint.class);
831 doReturn(pollLength).when(request).getDataLength();
832 doReturn(pollerFunctionCode).when(request).getFunctionCode();
834 PollTask task = Mockito.mock(PollTask.class);
835 doReturn(endpoint).when(task).getEndpoint();
836 doReturn(request).when(task).getRequest();
838 parent = createPollerMock("poller1", task);
841 String thingId = "read1";
843 ModbusDataThingHandler dataHandler = createDataHandler(thingId, parent,
844 builder -> builder.withConfiguration(config), bundleContext);
846 statusConsumer.accept(dataHandler.getThing().getStatusInfo());
850 public void testReadOnlyData() {
851 Configuration dataConfig = new Configuration();
852 dataConfig.put("readStart", "0");
853 dataConfig.put("readValueType", "bit");
854 testInitGeneric(ModbusReadFunctionCode.READ_COILS, dataConfig,
855 status -> assertThat(status.getStatus(), is(equalTo(ThingStatus.ONLINE))));
859 * readValueType=bit should be assumed with coils, so it's ok to skip it
862 public void testReadOnlyDataMissingValueTypeWithCoils() {
863 Configuration dataConfig = new Configuration();
864 dataConfig.put("readStart", "0");
865 // missing value type
866 testInitGeneric(ModbusReadFunctionCode.READ_COILS, dataConfig,
867 status -> assertThat(status.getStatus(), is(equalTo(ThingStatus.ONLINE))));
871 public void testReadOnlyDataInvalidValueType() {
872 Configuration dataConfig = new Configuration();
873 dataConfig.put("readStart", "0");
874 dataConfig.put("readValueType", "foobar");
875 testInitGeneric(ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS, dataConfig, status -> {
876 assertThat(status.getStatus(), is(equalTo(ThingStatus.OFFLINE)));
877 assertThat(status.getStatusDetail(), is(equalTo(ThingStatusDetail.CONFIGURATION_ERROR)));
882 * We do not assume value type with registers, not ok to skip it
885 public void testReadOnlyDataMissingValueTypeWithRegisters() {
886 Configuration dataConfig = new Configuration();
887 dataConfig.put("readStart", "0");
888 testInitGeneric(ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS, dataConfig, status -> {
889 assertThat(status.getStatus(), is(equalTo(ThingStatus.OFFLINE)));
890 assertThat(status.getStatusDetail(), is(equalTo(ThingStatusDetail.CONFIGURATION_ERROR)));
895 public void testWriteOnlyData() {
896 Configuration dataConfig = new Configuration();
897 dataConfig.put("writeStart", "0");
898 dataConfig.put("writeValueType", "bit");
899 dataConfig.put("writeType", "coil");
900 testInitGeneric(ModbusReadFunctionCode.READ_COILS, dataConfig,
901 status -> assertThat(status.getStatus(), is(equalTo(ThingStatus.ONLINE))));
905 public void testWriteHoldingInt16Data() {
906 Configuration dataConfig = new Configuration();
907 dataConfig.put("writeStart", "0");
908 dataConfig.put("writeValueType", "int16");
909 dataConfig.put("writeType", "holding");
910 testInitGeneric(ModbusReadFunctionCode.READ_COILS, dataConfig,
911 status -> assertThat(status.getStatus(), is(equalTo(ThingStatus.ONLINE))));
915 public void testWriteHoldingInt8Data() {
916 Configuration dataConfig = new Configuration();
917 dataConfig.put("writeStart", "0");
918 dataConfig.put("writeValueType", "int8");
919 dataConfig.put("writeType", "holding");
920 testInitGeneric(null, dataConfig, status -> {
921 assertThat(status.getStatus(), is(equalTo(ThingStatus.OFFLINE)));
922 assertThat(status.getStatusDetail(), is(equalTo(ThingStatusDetail.CONFIGURATION_ERROR)));
927 public void testWriteHoldingBitData() {
928 Configuration dataConfig = new Configuration();
929 dataConfig.put("writeStart", "0");
930 dataConfig.put("writeValueType", "bit");
931 dataConfig.put("writeType", "holding");
932 testInitGeneric(null, dataConfig, status -> {
933 assertThat(status.getStatus(), is(equalTo(ThingStatus.OFFLINE)));
934 assertThat(status.getStatusDetail(), is(equalTo(ThingStatusDetail.CONFIGURATION_ERROR)));
939 public void testWriteOnlyDataChildOfEndpoint() {
940 Configuration dataConfig = new Configuration();
941 dataConfig.put("writeStart", "0");
942 dataConfig.put("writeValueType", "bit");
943 dataConfig.put("writeType", "coil");
944 testInitGeneric(null, dataConfig, status -> assertThat(status.getStatus(), is(equalTo(ThingStatus.ONLINE))));
948 public void testWriteOnlyDataMissingOneParameter() {
949 Configuration dataConfig = new Configuration();
950 dataConfig.put("writeStart", "0");
951 dataConfig.put("writeValueType", "bit");
952 // missing writeType --> error
953 testInitGeneric(ModbusReadFunctionCode.READ_COILS, dataConfig, status -> {
954 assertThat(status.getStatus(), is(equalTo(ThingStatus.OFFLINE)));
955 assertThat(status.getStatusDetail(), is(equalTo(ThingStatusDetail.CONFIGURATION_ERROR)));
956 assertThat(status.getDescription(), is(not(equalTo(null))));
961 * OK to omit writeValueType with coils since bit is assumed
964 public void testWriteOnlyDataMissingValueTypeWithCoilParameter() {
965 Configuration dataConfig = new Configuration();
966 dataConfig.put("writeStart", "0");
967 dataConfig.put("writeType", "coil");
968 testInitGeneric(ModbusReadFunctionCode.READ_COILS, dataConfig,
969 status -> assertThat(status.getStatus(), is(equalTo(ThingStatus.ONLINE))));
973 public void testWriteOnlyIllegalValueType() {
974 Configuration dataConfig = new Configuration();
975 dataConfig.put("writeStart", "0");
976 dataConfig.put("writeType", "coil");
977 dataConfig.put("writeValueType", "foobar");
978 testInitGeneric(ModbusReadFunctionCode.READ_COILS, dataConfig, status -> {
979 assertThat(status.getStatus(), is(equalTo(ThingStatus.OFFLINE)));
980 assertThat(status.getStatusDetail(), is(equalTo(ThingStatusDetail.CONFIGURATION_ERROR)));
985 public void testWriteInvalidType() {
986 Configuration dataConfig = new Configuration();
987 dataConfig.put("writeStart", "0");
988 dataConfig.put("writeType", "foobar");
989 testInitGeneric(ModbusReadFunctionCode.READ_COILS, dataConfig, status -> {
990 assertThat(status.getStatus(), is(equalTo(ThingStatus.OFFLINE)));
991 assertThat(status.getStatusDetail(), is(equalTo(ThingStatusDetail.CONFIGURATION_ERROR)));
996 public void testWriteCoilBadStart() {
997 Configuration dataConfig = new Configuration();
998 dataConfig.put("writeStart", "0.4");
999 dataConfig.put("writeType", "coil");
1000 testInitGeneric(null, dataConfig, status -> {
1001 assertThat(status.getStatus(), is(equalTo(ThingStatus.OFFLINE)));
1002 assertThat(status.getStatusDetail(), is(equalTo(ThingStatusDetail.CONFIGURATION_ERROR)));
1007 public void testWriteHoldingBadStart() {
1008 Configuration dataConfig = new Configuration();
1009 dataConfig.put("writeStart", "0.4");
1010 dataConfig.put("writeType", "holding");
1011 testInitGeneric(null, dataConfig, status -> {
1012 assertThat(status.getStatus(), is(equalTo(ThingStatus.OFFLINE)));
1013 assertThat(status.getStatusDetail(), is(equalTo(ThingStatusDetail.CONFIGURATION_ERROR)));
1018 public void testReadHoldingBadStart() {
1019 Configuration dataConfig = new Configuration();
1020 dataConfig.put("readStart", "0.0");
1021 dataConfig.put("readValueType", "int16");
1022 testInitGeneric(ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS, dataConfig, status -> {
1023 assertThat(status.getStatus(), is(equalTo(ThingStatus.OFFLINE)));
1024 assertThat(status.getStatusDetail(), is(equalTo(ThingStatusDetail.CONFIGURATION_ERROR)));
1029 public void testReadHoldingBadStart2() {
1030 Configuration dataConfig = new Configuration();
1031 dataConfig.put("readStart", "0.0");
1032 dataConfig.put("readValueType", "bit");
1033 testInitGeneric(ModbusReadFunctionCode.READ_COILS, dataConfig, status -> {
1034 assertThat(status.getStatus(), is(equalTo(ThingStatus.OFFLINE)));
1035 assertThat(status.getStatusDetail(), is(equalTo(ThingStatusDetail.CONFIGURATION_ERROR)));
1040 public void testReadHoldingOKStart() {
1041 Configuration dataConfig = new Configuration();
1042 dataConfig.put("readStart", "0.0");
1043 dataConfig.put("readType", "holding");
1044 dataConfig.put("readValueType", "bit");
1045 testInitGeneric(ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS, dataConfig,
1046 status -> assertThat(status.getStatus(), is(equalTo(ThingStatus.ONLINE))));
1050 public void testReadValueTypeIllegal() {
1051 Configuration dataConfig = new Configuration();
1052 dataConfig.put("readStart", "0.0");
1053 dataConfig.put("readType", "holding");
1054 dataConfig.put("readValueType", "foobar");
1055 testInitGeneric(ModbusReadFunctionCode.READ_COILS, dataConfig, status -> {
1056 assertThat(status.getStatus(), is(equalTo(ThingStatus.OFFLINE)));
1057 assertThat(status.getStatusDetail(), is(equalTo(ThingStatusDetail.CONFIGURATION_ERROR)));
1062 public void testWriteOnlyTransform() {
1063 Configuration dataConfig = new Configuration();
1064 // no need to have start, JSON output of transformation defines everything
1065 dataConfig.put("writeTransform", "JS(myJsonTransform.js)");
1066 testInitGeneric(null, dataConfig, status -> assertThat(status.getStatus(), is(equalTo(ThingStatus.ONLINE))));
1070 public void testWriteTransformAndStart() {
1071 Configuration dataConfig = new Configuration();
1072 // It's illegal to have start and transform. Just have transform or have all
1073 dataConfig.put("writeStart", "3");
1074 dataConfig.put("writeTransform", "JS(myJsonTransform.js)");
1075 testInitGeneric(ModbusReadFunctionCode.READ_COILS, dataConfig, status -> {
1076 assertThat(status.getStatus(), is(equalTo(ThingStatus.OFFLINE)));
1077 assertThat(status.getStatusDetail(), is(equalTo(ThingStatusDetail.CONFIGURATION_ERROR)));
1082 public void testWriteTransformAndNecessary() {
1083 Configuration dataConfig = new Configuration();
1084 // It's illegal to have start and transform. Just have transform or have all
1085 dataConfig.put("writeStart", "3");
1086 dataConfig.put("writeType", "holding");
1087 dataConfig.put("writeValueType", "int16");
1088 dataConfig.put("writeTransform", "JS(myJsonTransform.js)");
1089 testInitGeneric(null, dataConfig, status -> assertThat(status.getStatus(), is(equalTo(ThingStatus.ONLINE))));