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.binding.modbus.tests;
15 import static org.hamcrest.CoreMatchers.*;
16 import static org.junit.Assert.*;
17 import static org.mockito.ArgumentMatchers.*;
18 import static org.mockito.ArgumentMatchers.any;
19 import static org.mockito.Mockito.*;
20 import static org.openhab.binding.modbus.internal.ModbusBindingConstantsInternal.*;
22 import java.util.ArrayList;
23 import java.util.Arrays;
24 import java.util.HashMap;
25 import java.util.List;
27 import java.util.Map.Entry;
28 import java.util.concurrent.ScheduledFuture;
29 import java.util.function.Consumer;
30 import java.util.function.Function;
32 import org.apache.commons.lang.StringUtils;
33 import org.openhab.core.config.core.Configuration;
34 import org.openhab.core.items.GenericItem;
35 import org.openhab.core.items.Item;
36 import org.openhab.core.library.items.DateTimeItem;
37 import org.openhab.core.library.types.DecimalType;
38 import org.openhab.core.library.types.OnOffType;
39 import org.openhab.core.library.types.OpenClosedType;
40 import org.openhab.core.library.types.StringType;
41 import org.openhab.core.thing.Bridge;
42 import org.openhab.core.thing.ChannelUID;
43 import org.openhab.core.thing.Thing;
44 import org.openhab.core.thing.ThingStatus;
45 import org.openhab.core.thing.ThingStatusDetail;
46 import org.openhab.core.thing.ThingStatusInfo;
47 import org.openhab.core.thing.ThingUID;
48 import org.openhab.core.thing.binding.ThingHandler;
49 import org.openhab.core.thing.binding.builder.BridgeBuilder;
50 import org.openhab.core.thing.binding.builder.ChannelBuilder;
51 import org.openhab.core.thing.binding.builder.ThingBuilder;
52 import org.openhab.core.transform.TransformationException;
53 import org.openhab.core.transform.TransformationService;
54 import org.openhab.core.types.Command;
55 import org.openhab.core.types.RefreshType;
56 import org.openhab.core.types.State;
57 import org.openhab.core.types.UnDefType;
58 import org.hamcrest.Matcher;
59 import org.junit.After;
60 import org.junit.Test;
61 import org.junit.runner.RunWith;
62 import org.mockito.Mockito;
63 import org.mockito.junit.MockitoJUnitRunner;
64 import org.openhab.binding.modbus.handler.EndpointNotInitializedException;
65 import org.openhab.binding.modbus.handler.ModbusPollerThingHandler;
66 import org.openhab.binding.modbus.internal.handler.ModbusDataThingHandler;
67 import org.openhab.binding.modbus.internal.handler.ModbusTcpThingHandler;
68 import org.openhab.io.transport.modbus.AsyncModbusFailure;
69 import org.openhab.io.transport.modbus.AsyncModbusReadResult;
70 import org.openhab.io.transport.modbus.AsyncModbusWriteResult;
71 import org.openhab.io.transport.modbus.BitArray;
72 import org.openhab.io.transport.modbus.ModbusConstants;
73 import org.openhab.io.transport.modbus.ModbusConstants.ValueType;
74 import org.openhab.io.transport.modbus.ModbusReadFunctionCode;
75 import org.openhab.io.transport.modbus.ModbusReadRequestBlueprint;
76 import org.openhab.io.transport.modbus.ModbusRegister;
77 import org.openhab.io.transport.modbus.ModbusRegisterArray;
78 import org.openhab.io.transport.modbus.ModbusResponse;
79 import org.openhab.io.transport.modbus.ModbusWriteCoilRequestBlueprint;
80 import org.openhab.io.transport.modbus.ModbusWriteFunctionCode;
81 import org.openhab.io.transport.modbus.ModbusWriteRegisterRequestBlueprint;
82 import org.openhab.io.transport.modbus.ModbusWriteRequestBlueprint;
83 import org.openhab.io.transport.modbus.PollTask;
84 import org.openhab.io.transport.modbus.endpoint.ModbusSlaveEndpoint;
85 import org.openhab.io.transport.modbus.endpoint.ModbusTCPSlaveEndpoint;
86 import org.osgi.framework.BundleContext;
87 import org.osgi.framework.InvalidSyntaxException;
90 * @author Sami Salonen - Initial contribution
92 @RunWith(MockitoJUnitRunner.class)
93 public class ModbusDataHandlerTest extends AbstractModbusOSGiTest {
95 private final class MultiplyTransformation implements TransformationService {
97 public String transform(String function, String source) throws TransformationException {
98 return String.valueOf(Integer.parseInt(function) * Integer.parseInt(source));
102 private static final Map<String, String> CHANNEL_TO_ACCEPTED_TYPE = new HashMap<>();
104 CHANNEL_TO_ACCEPTED_TYPE.put(CHANNEL_SWITCH, "Switch");
105 CHANNEL_TO_ACCEPTED_TYPE.put(CHANNEL_CONTACT, "Contact");
106 CHANNEL_TO_ACCEPTED_TYPE.put(CHANNEL_DATETIME, "DateTime");
107 CHANNEL_TO_ACCEPTED_TYPE.put(CHANNEL_DIMMER, "Dimmer");
108 CHANNEL_TO_ACCEPTED_TYPE.put(CHANNEL_NUMBER, "Number");
109 CHANNEL_TO_ACCEPTED_TYPE.put(CHANNEL_STRING, "String");
110 CHANNEL_TO_ACCEPTED_TYPE.put(CHANNEL_ROLLERSHUTTER, "Rollershutter");
111 CHANNEL_TO_ACCEPTED_TYPE.put(CHANNEL_LAST_READ_SUCCESS, "DateTime");
112 CHANNEL_TO_ACCEPTED_TYPE.put(CHANNEL_LAST_WRITE_SUCCESS, "DateTime");
113 CHANNEL_TO_ACCEPTED_TYPE.put(CHANNEL_LAST_WRITE_ERROR, "DateTime");
114 CHANNEL_TO_ACCEPTED_TYPE.put(CHANNEL_LAST_READ_ERROR, "DateTime");
116 private List<ModbusWriteRequestBlueprint> writeRequests = new ArrayList<>();
119 public void tearDown() {
120 writeRequests.clear();
123 private void captureModbusWrites() {
124 Mockito.when(comms.submitOneTimeWrite(any(), any(), any())).then(invocation -> {
125 ModbusWriteRequestBlueprint task = (ModbusWriteRequestBlueprint) invocation.getArgument(0);
126 writeRequests.add(task);
127 return Mockito.mock(ScheduledFuture.class);
131 private Bridge createPollerMock(String pollerId, PollTask task) {
133 ThingUID thingUID = new ThingUID(THING_TYPE_MODBUS_POLLER, pollerId);
134 BridgeBuilder builder = BridgeBuilder.create(THING_TYPE_MODBUS_POLLER, thingUID)
135 .withLabel("label for " + pollerId);
136 for (Entry<String, String> entry : CHANNEL_TO_ACCEPTED_TYPE.entrySet()) {
137 String channelId = entry.getKey();
138 String channelAcceptedType = entry.getValue();
139 builder = builder.withChannel(
140 ChannelBuilder.create(new ChannelUID(thingUID, channelId), channelAcceptedType).build());
142 poller = builder.build();
143 poller.setStatusInfo(new ThingStatusInfo(ThingStatus.ONLINE, ThingStatusDetail.NONE, ""));
145 ModbusPollerThingHandler mockHandler = Mockito.mock(ModbusPollerThingHandler.class);
146 doReturn(task.getRequest()).when(mockHandler).getRequest();
147 assert comms != null;
148 doReturn(comms).when(mockHandler).getCommunicationInterface();
149 doReturn(task.getEndpoint()).when(comms).getEndpoint();
150 poller.setHandler(mockHandler);
151 assertSame(poller.getHandler(), mockHandler);
152 assertSame(((ModbusPollerThingHandler) poller.getHandler()).getCommunicationInterface().getEndpoint(),
154 assertSame(((ModbusPollerThingHandler) poller.getHandler()).getRequest(), task.getRequest());
160 private Bridge createTcpMock() {
161 ModbusSlaveEndpoint endpoint = new ModbusTCPSlaveEndpoint("thisishost", 502);
162 Bridge tcpBridge = ModbusPollerThingHandlerTest.createTcpThingBuilder("tcp1").build();
163 ModbusTcpThingHandler tcpThingHandler = Mockito.mock(ModbusTcpThingHandler.class);
164 tcpBridge.setStatusInfo(new ThingStatusInfo(ThingStatus.ONLINE, ThingStatusDetail.NONE, ""));
165 tcpBridge.setHandler(tcpThingHandler);
166 doReturn(comms).when(tcpThingHandler).getCommunicationInterface();
168 doReturn(0).when(tcpThingHandler).getSlaveId();
169 } catch (EndpointNotInitializedException e) {
170 // not raised -- we are mocking return value only, not actually calling the method
171 throw new IllegalStateException();
173 tcpThingHandler.initialize();
174 assertThat(tcpBridge.getStatus(), is(equalTo(ThingStatus.ONLINE)));
178 private ModbusDataThingHandler createDataHandler(String id, Bridge bridge,
179 Function<ThingBuilder, ThingBuilder> builderConfigurator) {
180 return createDataHandler(id, bridge, builderConfigurator, null);
183 private ModbusDataThingHandler createDataHandler(String id, Bridge bridge,
184 Function<ThingBuilder, ThingBuilder> builderConfigurator, BundleContext context) {
185 return createDataHandler(id, bridge, builderConfigurator, context, true);
188 private ModbusDataThingHandler createDataHandler(String id, Bridge bridge,
189 Function<ThingBuilder, ThingBuilder> builderConfigurator, BundleContext context,
190 boolean autoCreateItemsAndLinkToChannels) {
191 ThingUID thingUID = new ThingUID(THING_TYPE_MODBUS_DATA, id);
192 ThingBuilder builder = ThingBuilder.create(THING_TYPE_MODBUS_DATA, thingUID).withLabel("label for " + id);
193 Map<String, ChannelUID> toBeLinked = new HashMap<>();
194 for (Entry<String, String> entry : CHANNEL_TO_ACCEPTED_TYPE.entrySet()) {
195 String channelId = entry.getKey();
196 String channelAcceptedType = entry.getValue();
197 ChannelUID channelUID = new ChannelUID(thingUID, channelId);
198 builder = builder.withChannel(ChannelBuilder.create(channelUID, channelAcceptedType).build());
200 if (autoCreateItemsAndLinkToChannels) {
201 // Create item of correct type and link it to channel
202 String itemName = getItemName(channelUID);
203 final GenericItem item;
204 if (channelId.startsWith("last") || channelId.equals("datetime")) {
205 item = new DateTimeItem(itemName);
207 item = coreItemFactory.createItem(StringUtils.capitalize(channelId), itemName);
209 assertThat(String.format("Could not determine correct item type for %s", channelId), item,
213 toBeLinked.put(itemName, channelUID);
216 if (builderConfigurator != null) {
217 builder = builderConfigurator.apply(builder);
220 Thing dataThing = builder.withBridge(bridge.getUID()).build();
223 // Link after the things and items have been created
224 for (Entry<String, ChannelUID> entry : toBeLinked.entrySet()) {
225 linkItem(entry.getKey(), entry.getValue());
227 return (ModbusDataThingHandler) dataThing.getHandler();
230 private String getItemName(ChannelUID channelUID) {
231 return channelUID.toString().replace(':', '_') + "_item";
234 private void assertSingleStateUpdate(ModbusDataThingHandler handler, String channel, Matcher<State> matcher) {
235 waitForAssert(() -> {
236 ChannelUID channelUID = new ChannelUID(handler.getThing().getUID(), channel);
237 String itemName = getItemName(channelUID);
238 Item item = itemRegistry.get(itemName);
239 assertThat(String.format("Item %s is not available from item registry", itemName), item,
242 List<State> updates = getStateUpdates(itemName);
243 if (updates != null) {
245 String.format("Many updates found, expected one: %s", Arrays.deepToString(updates.toArray())),
246 updates.size(), is(equalTo(1)));
248 State state = updates == null ? null : updates.get(0);
249 assertThat(String.format("%s %s, state %s of type %s", item.getClass().getSimpleName(), itemName, state,
250 state == null ? null : state.getClass().getSimpleName()), state, is(matcher));
254 private void assertSingleStateUpdate(ModbusDataThingHandler handler, String channel, State state) {
255 assertSingleStateUpdate(handler, channel, is(equalTo(state)));
258 private void testOutOfBoundsGeneric(int pollStart, int pollLength, String start,
259 ModbusReadFunctionCode functionCode, ValueType valueType, ThingStatus expectedStatus) {
260 testOutOfBoundsGeneric(pollStart, pollLength, start, functionCode, valueType, expectedStatus, null);
263 private void testOutOfBoundsGeneric(int pollStart, int pollLength, String start,
264 ModbusReadFunctionCode functionCode, ValueType valueType, ThingStatus expectedStatus,
265 BundleContext context) {
266 ModbusSlaveEndpoint endpoint = new ModbusTCPSlaveEndpoint("thisishost", 502);
268 // Minimally mocked request
269 ModbusReadRequestBlueprint request = Mockito.mock(ModbusReadRequestBlueprint.class);
270 doReturn(pollStart).when(request).getReference();
271 doReturn(pollLength).when(request).getDataLength();
272 doReturn(functionCode).when(request).getFunctionCode();
274 PollTask task = Mockito.mock(PollTask.class);
275 doReturn(endpoint).when(task).getEndpoint();
276 doReturn(request).when(task).getRequest();
278 Bridge pollerThing = createPollerMock("poller1", task);
280 Configuration dataConfig = new Configuration();
281 dataConfig.put("readStart", start);
282 dataConfig.put("readTransform", "default");
283 dataConfig.put("readValueType", valueType.getConfigValue());
284 ModbusDataThingHandler dataHandler = createDataHandler("data1", pollerThing,
285 builder -> builder.withConfiguration(dataConfig), context);
286 assertThat(dataHandler.getThing().getStatusInfo().getDescription(), dataHandler.getThing().getStatus(),
287 is(equalTo(expectedStatus)));
291 public void testInitCoilsOutOfIndex() {
292 testOutOfBoundsGeneric(4, 3, "8", ModbusReadFunctionCode.READ_COILS, ModbusConstants.ValueType.BIT,
293 ThingStatus.OFFLINE);
297 public void testInitCoilsOutOfIndex2() {
298 // Reading coils 4, 5, 6. Coil 7 is out of bounds
299 testOutOfBoundsGeneric(4, 3, "7", ModbusReadFunctionCode.READ_COILS, ModbusConstants.ValueType.BIT,
300 ThingStatus.OFFLINE);
304 public void testInitCoilsOK() {
305 // Reading coils 4, 5, 6. Coil 6 is OK
306 testOutOfBoundsGeneric(4, 3, "6", ModbusReadFunctionCode.READ_COILS, ModbusConstants.ValueType.BIT,
311 public void testInitRegistersWithBitOutOfIndex() {
312 testOutOfBoundsGeneric(4, 3, "8.0", ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS,
313 ModbusConstants.ValueType.BIT, ThingStatus.OFFLINE);
317 public void testInitRegistersWithBitOutOfIndex2() {
318 testOutOfBoundsGeneric(4, 3, "7.16", ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS,
319 ModbusConstants.ValueType.BIT, ThingStatus.OFFLINE);
323 public void testInitRegistersWithBitOK() {
324 testOutOfBoundsGeneric(4, 3, "6.0", ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS,
325 ModbusConstants.ValueType.BIT, ThingStatus.ONLINE);
329 public void testInitRegistersWithBitOK2() {
330 testOutOfBoundsGeneric(4, 3, "6.15", ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS,
331 ModbusConstants.ValueType.BIT, ThingStatus.ONLINE);
335 public void testInitRegistersWithInt8OutOfIndex() {
336 testOutOfBoundsGeneric(4, 3, "8.0", ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS,
337 ModbusConstants.ValueType.INT8, ThingStatus.OFFLINE);
341 public void testInitRegistersWithInt8OutOfIndex2() {
342 testOutOfBoundsGeneric(4, 3, "7.2", ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS,
343 ModbusConstants.ValueType.INT8, ThingStatus.OFFLINE);
347 public void testInitRegistersWithInt8OK() {
348 testOutOfBoundsGeneric(4, 3, "6.0", ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS,
349 ModbusConstants.ValueType.INT8, ThingStatus.ONLINE);
353 public void testInitRegistersWithInt8OK2() {
354 testOutOfBoundsGeneric(4, 3, "6.1", ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS,
355 ModbusConstants.ValueType.INT8, ThingStatus.ONLINE);
359 public void testInitRegistersWithInt16OK() {
360 // Poller reading registers 4, 5, 6. Register 6 is OK
361 testOutOfBoundsGeneric(4, 3, "6", ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS,
362 ModbusConstants.ValueType.INT16, ThingStatus.ONLINE);
366 public void testInitRegistersWithInt16OutOfBounds() {
367 // Poller reading registers 4, 5, 6. Register 7 is out-of-bounds
368 testOutOfBoundsGeneric(4, 3, "7", ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS,
369 ModbusConstants.ValueType.INT16, ThingStatus.OFFLINE);
373 public void testInitRegistersWithInt16OutOfBounds2() {
374 testOutOfBoundsGeneric(4, 3, "8", ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS,
375 ModbusConstants.ValueType.INT16, ThingStatus.OFFLINE);
379 public void testInitRegistersWithInt16NoDecimalFormatAllowed() {
380 testOutOfBoundsGeneric(4, 3, "7.0", ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS,
381 ModbusConstants.ValueType.INT16, ThingStatus.OFFLINE);
385 public void testInitRegistersWithInt32OK() {
386 testOutOfBoundsGeneric(4, 3, "5", ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS,
387 ModbusConstants.ValueType.INT32, ThingStatus.ONLINE);
391 public void testInitRegistersWithInt32OutOfBounds() {
392 testOutOfBoundsGeneric(4, 3, "6", ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS,
393 ModbusConstants.ValueType.INT32, ThingStatus.OFFLINE);
397 public void testInitRegistersWithInt32AtTheEdge() {
398 testOutOfBoundsGeneric(4, 3, "5", ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS,
399 ModbusConstants.ValueType.INT32, ThingStatus.ONLINE);
402 private ModbusDataThingHandler testReadHandlingGeneric(ModbusReadFunctionCode functionCode, String start,
403 String transform, ValueType valueType, BitArray bits, ModbusRegisterArray registers, Exception error) {
404 return testReadHandlingGeneric(functionCode, start, transform, valueType, bits, registers, error, null);
407 private ModbusDataThingHandler testReadHandlingGeneric(ModbusReadFunctionCode functionCode, String start,
408 String transform, ValueType valueType, BitArray bits, ModbusRegisterArray registers, Exception error,
409 BundleContext context) {
410 return testReadHandlingGeneric(functionCode, start, transform, valueType, bits, registers, error, context,
414 @SuppressWarnings({ "null" })
415 private ModbusDataThingHandler testReadHandlingGeneric(ModbusReadFunctionCode functionCode, String start,
416 String transform, ValueType valueType, BitArray bits, ModbusRegisterArray registers, Exception error,
417 BundleContext context, boolean autoCreateItemsAndLinkToChannels) {
418 ModbusSlaveEndpoint endpoint = new ModbusTCPSlaveEndpoint("thisishost", 502);
422 // Minimally mocked request
423 ModbusReadRequestBlueprint request = Mockito.mock(ModbusReadRequestBlueprint.class);
424 doReturn(pollLength).when(request).getDataLength();
425 doReturn(functionCode).when(request).getFunctionCode();
427 PollTask task = Mockito.mock(PollTask.class);
428 doReturn(endpoint).when(task).getEndpoint();
429 doReturn(request).when(task).getRequest();
431 Bridge poller = createPollerMock("poller1", task);
433 Configuration dataConfig = new Configuration();
434 dataConfig.put("readStart", start);
435 dataConfig.put("readTransform", transform);
436 dataConfig.put("readValueType", valueType.getConfigValue());
438 String thingId = "read1";
439 ModbusDataThingHandler dataHandler = createDataHandler(thingId, poller,
440 builder -> builder.withConfiguration(dataConfig), context, autoCreateItemsAndLinkToChannels);
442 assertThat(dataHandler.getThing().getStatus(), is(equalTo(ThingStatus.ONLINE)));
446 assertNull(registers);
448 AsyncModbusReadResult result = new AsyncModbusReadResult(request, bits);
449 dataHandler.onReadResult(result);
450 } else if (registers != null) {
453 AsyncModbusReadResult result = new AsyncModbusReadResult(request, registers);
454 dataHandler.onReadResult(result);
457 assertNull(registers);
458 assertNotNull(error);
459 AsyncModbusFailure<ModbusReadRequestBlueprint> result = new AsyncModbusFailure<ModbusReadRequestBlueprint>(
461 dataHandler.handleReadError(result);
466 @SuppressWarnings({ "null" })
467 private ModbusDataThingHandler testWriteHandlingGeneric(String start, String transform, ValueType valueType,
468 String writeType, ModbusWriteFunctionCode successFC, String channel, Command command, Exception error,
469 BundleContext context) {
470 ModbusSlaveEndpoint endpoint = new ModbusTCPSlaveEndpoint("thisishost", 502);
472 // Minimally mocked request
473 ModbusReadRequestBlueprint request = Mockito.mock(ModbusReadRequestBlueprint.class);
475 PollTask task = Mockito.mock(PollTask.class);
476 doReturn(endpoint).when(task).getEndpoint();
477 doReturn(request).when(task).getRequest();
479 Bridge poller = createPollerMock("poller1", task);
481 Configuration dataConfig = new Configuration();
482 dataConfig.put("readStart", "");
483 dataConfig.put("writeStart", start);
484 dataConfig.put("writeTransform", transform);
485 dataConfig.put("writeValueType", valueType.getConfigValue());
486 dataConfig.put("writeType", writeType);
488 String thingId = "write";
490 ModbusDataThingHandler dataHandler = createDataHandler(thingId, poller,
491 builder -> builder.withConfiguration(dataConfig), context);
493 assertThat(dataHandler.getThing().getStatus(), is(equalTo(ThingStatus.ONLINE)));
495 dataHandler.handleCommand(new ChannelUID(dataHandler.getThing().getUID(), channel), command);
498 dataHandler.handleReadError(new AsyncModbusFailure<ModbusReadRequestBlueprint>(request, error));
500 ModbusResponse resp = new ModbusResponse() {
503 public int getFunctionCode() {
504 return successFC.getFunctionCode();
508 .onWriteResponse(new AsyncModbusWriteResult(Mockito.mock(ModbusWriteRequestBlueprint.class), resp));
514 public void testOnError() {
515 ModbusDataThingHandler dataHandler = testReadHandlingGeneric(ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS,
516 "0.0", "default", ModbusConstants.ValueType.BIT, null, null, new Exception("fooerror"));
518 assertSingleStateUpdate(dataHandler, CHANNEL_LAST_READ_ERROR, is(notNullValue(State.class)));
522 public void testOnRegistersInt16StaticTransformation() {
523 ModbusDataThingHandler dataHandler = testReadHandlingGeneric(ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS,
524 "0", "-3", ModbusConstants.ValueType.INT16, null,
525 new ModbusRegisterArray(new ModbusRegister[] { new ModbusRegister((byte) 0xff, (byte) 0xfd) }), null);
527 assertSingleStateUpdate(dataHandler, CHANNEL_LAST_READ_SUCCESS, is(notNullValue(State.class)));
528 assertSingleStateUpdate(dataHandler, CHANNEL_LAST_READ_ERROR, is(nullValue(State.class)));
530 // -3 converts to "true"
531 assertSingleStateUpdate(dataHandler, CHANNEL_CONTACT, is(nullValue(State.class)));
532 assertSingleStateUpdate(dataHandler, CHANNEL_SWITCH, is(nullValue(State.class)));
533 assertSingleStateUpdate(dataHandler, CHANNEL_DIMMER, is(nullValue(State.class)));
534 assertSingleStateUpdate(dataHandler, CHANNEL_NUMBER, new DecimalType(-3));
535 // roller shutter fails since -3 is invalid value (not between 0...100)
536 // assertThatStateContains(state, CHANNEL_ROLLERSHUTTER, new PercentType(1));
537 assertSingleStateUpdate(dataHandler, CHANNEL_STRING, new StringType("-3"));
538 // no datetime, conversion not possible without transformation
542 public void testOnRegistersRealTransformation() {
543 mockTransformation("MULTIPLY", new MultiplyTransformation());
544 ModbusDataThingHandler dataHandler = testReadHandlingGeneric(ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS,
545 "0", "MULTIPLY(10)", ModbusConstants.ValueType.INT16, null,
546 new ModbusRegisterArray(new ModbusRegister[] { new ModbusRegister((byte) 0xff, (byte) 0xfd) }), null,
549 assertSingleStateUpdate(dataHandler, CHANNEL_LAST_READ_SUCCESS, is(notNullValue(State.class)));
550 assertSingleStateUpdate(dataHandler, CHANNEL_LAST_READ_ERROR, is(nullValue(State.class)));
552 // transformation output (-30) is not valid for contact or switch
553 assertSingleStateUpdate(dataHandler, CHANNEL_CONTACT, is(nullValue(State.class)));
554 assertSingleStateUpdate(dataHandler, CHANNEL_SWITCH, is(nullValue(State.class)));
555 // -30 is not valid value for Dimmer (PercentType) (not between 0...100)
556 assertSingleStateUpdate(dataHandler, CHANNEL_DIMMER, is(nullValue(State.class)));
557 assertSingleStateUpdate(dataHandler, CHANNEL_NUMBER, new DecimalType(-30));
558 // roller shutter fails since -3 is invalid value (not between 0...100)
559 assertSingleStateUpdate(dataHandler, CHANNEL_ROLLERSHUTTER, is(nullValue(State.class)));
560 assertSingleStateUpdate(dataHandler, CHANNEL_STRING, new StringType("-30"));
561 // no datetime, conversion not possible without transformation
565 public void testOnRegistersNaNFloatInRegisters() throws InvalidSyntaxException {
566 ModbusDataThingHandler dataHandler = testReadHandlingGeneric(ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS,
567 "0", "default", ModbusConstants.ValueType.FLOAT32, null, new ModbusRegisterArray(
568 // equivalent of floating point NaN
569 new ModbusRegister[] { new ModbusRegister((byte) 0x7f, (byte) 0xc0),
570 new ModbusRegister((byte) 0x00, (byte) 0x00) }),
571 null, bundleContext);
573 assertSingleStateUpdate(dataHandler, CHANNEL_LAST_READ_SUCCESS, is(notNullValue(State.class)));
574 assertSingleStateUpdate(dataHandler, CHANNEL_LAST_READ_ERROR, is(nullValue(State.class)));
576 // UNDEF is treated as "boolean true" (OPEN/ON) since it is != 0.
577 assertSingleStateUpdate(dataHandler, CHANNEL_CONTACT, OpenClosedType.OPEN);
578 assertSingleStateUpdate(dataHandler, CHANNEL_SWITCH, OnOffType.ON);
579 assertSingleStateUpdate(dataHandler, CHANNEL_DIMMER, OnOffType.ON);
580 assertSingleStateUpdate(dataHandler, CHANNEL_NUMBER, UnDefType.UNDEF);
581 assertSingleStateUpdate(dataHandler, CHANNEL_ROLLERSHUTTER, UnDefType.UNDEF);
582 assertSingleStateUpdate(dataHandler, CHANNEL_STRING, UnDefType.UNDEF);
586 public void testOnRegistersRealTransformation2() throws InvalidSyntaxException {
587 mockTransformation("ONOFF", new TransformationService() {
590 public String transform(String function, String source) throws TransformationException {
591 return Integer.parseInt(source) != 0 ? "ON" : "OFF";
594 ModbusDataThingHandler dataHandler = testReadHandlingGeneric(ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS,
595 "0", "ONOFF(10)", ModbusConstants.ValueType.INT16, null,
596 new ModbusRegisterArray(new ModbusRegister[] { new ModbusRegister((byte) 0xff, (byte) 0xfd) }), null,
599 assertSingleStateUpdate(dataHandler, CHANNEL_LAST_READ_SUCCESS, is(notNullValue(State.class)));
600 assertSingleStateUpdate(dataHandler, CHANNEL_LAST_READ_ERROR, is(nullValue(State.class)));
602 assertSingleStateUpdate(dataHandler, CHANNEL_CONTACT, is(nullValue(State.class)));
603 assertSingleStateUpdate(dataHandler, CHANNEL_SWITCH, is(equalTo(OnOffType.ON)));
604 assertSingleStateUpdate(dataHandler, CHANNEL_DIMMER, is(equalTo(OnOffType.ON)));
605 assertSingleStateUpdate(dataHandler, CHANNEL_NUMBER, is(nullValue(State.class)));
606 assertSingleStateUpdate(dataHandler, CHANNEL_ROLLERSHUTTER, is(nullValue(State.class)));
607 assertSingleStateUpdate(dataHandler, CHANNEL_STRING, is(equalTo(new StringType("ON"))));
611 public void testWriteRealTransformation() throws InvalidSyntaxException {
612 captureModbusWrites();
613 mockTransformation("MULTIPLY", new MultiplyTransformation());
614 ModbusDataThingHandler dataHandler = testWriteHandlingGeneric("50", "MULTIPLY(10)",
615 ModbusConstants.ValueType.BIT, "coil", ModbusWriteFunctionCode.WRITE_COIL, "number",
616 new DecimalType("2"), null, bundleContext);
618 assertSingleStateUpdate(dataHandler, CHANNEL_LAST_WRITE_SUCCESS, is(notNullValue(State.class)));
619 assertSingleStateUpdate(dataHandler, CHANNEL_LAST_WRITE_ERROR, is(nullValue(State.class)));
620 assertThat(writeRequests.size(), is(equalTo(1)));
621 ModbusWriteRequestBlueprint writeRequest = writeRequests.get(0);
622 assertThat(writeRequest.getFunctionCode(), is(equalTo(ModbusWriteFunctionCode.WRITE_COIL)));
623 assertThat(writeRequest.getReference(), is(equalTo(50)));
624 assertThat(((ModbusWriteCoilRequestBlueprint) writeRequest).getCoils().size(), is(equalTo(1)));
625 // Since transform output is non-zero, it is mapped as "true"
626 assertThat(((ModbusWriteCoilRequestBlueprint) writeRequest).getCoils().getBit(0), is(equalTo(true)));
630 public void testWriteRealTransformation2() throws InvalidSyntaxException {
631 captureModbusWrites();
632 mockTransformation("ZERO", new TransformationService() {
635 public String transform(String function, String source) throws TransformationException {
639 ModbusDataThingHandler dataHandler = testWriteHandlingGeneric("50", "ZERO(foobar)",
640 ModbusConstants.ValueType.BIT, "coil", ModbusWriteFunctionCode.WRITE_COIL, "number",
641 new DecimalType("2"), null, bundleContext);
643 assertSingleStateUpdate(dataHandler, CHANNEL_LAST_WRITE_SUCCESS, is(notNullValue(State.class)));
644 assertSingleStateUpdate(dataHandler, CHANNEL_LAST_WRITE_ERROR, is(nullValue(State.class)));
645 assertThat(writeRequests.size(), is(equalTo(1)));
646 ModbusWriteRequestBlueprint writeRequest = writeRequests.get(0);
647 assertThat(writeRequest.getFunctionCode(), is(equalTo(ModbusWriteFunctionCode.WRITE_COIL)));
648 assertThat(writeRequest.getReference(), is(equalTo(50)));
649 assertThat(((ModbusWriteCoilRequestBlueprint) writeRequest).getCoils().size(), is(equalTo(1)));
650 // Since transform output is zero, it is mapped as "false"
651 assertThat(((ModbusWriteCoilRequestBlueprint) writeRequest).getCoils().getBit(0), is(equalTo(false)));
655 public void testWriteRealTransformation3() throws InvalidSyntaxException {
656 captureModbusWrites();
657 mockTransformation("RANDOM", new TransformationService() {
660 public String transform(String function, String source) throws TransformationException {
664 ModbusDataThingHandler dataHandler = testWriteHandlingGeneric("50", "RANDOM(foobar)",
665 ModbusConstants.ValueType.INT16, "holding", ModbusWriteFunctionCode.WRITE_SINGLE_REGISTER, "number",
666 new DecimalType("2"), null, bundleContext);
668 assertSingleStateUpdate(dataHandler, CHANNEL_LAST_WRITE_SUCCESS, is(notNullValue(State.class)));
669 assertSingleStateUpdate(dataHandler, CHANNEL_LAST_WRITE_ERROR, is(nullValue(State.class)));
670 assertThat(writeRequests.size(), is(equalTo(1)));
671 ModbusWriteRequestBlueprint writeRequest = writeRequests.get(0);
672 assertThat(writeRequest.getFunctionCode(), is(equalTo(ModbusWriteFunctionCode.WRITE_SINGLE_REGISTER)));
673 assertThat(writeRequest.getReference(), is(equalTo(50)));
674 assertThat(((ModbusWriteRegisterRequestBlueprint) writeRequest).getRegisters().size(), is(equalTo(1)));
675 assertThat(((ModbusWriteRegisterRequestBlueprint) writeRequest).getRegisters().getRegister(0).getValue(),
680 public void testWriteRealTransformation4() throws InvalidSyntaxException {
681 captureModbusWrites();
682 mockTransformation("JSON", new TransformationService() {
685 public String transform(String function, String source) throws TransformationException {
687 + "\"functionCode\": 16,"//
688 + "\"address\": 5412,"//
689 + "\"value\": [1, 0, 5]"//
692 + "\"functionCode\": 6,"//
693 + "\"address\": 555,"//
698 ModbusDataThingHandler dataHandler = testWriteHandlingGeneric("50", "JSON(foobar)",
699 ModbusConstants.ValueType.INT16, "holding", ModbusWriteFunctionCode.WRITE_MULTIPLE_REGISTERS, "number",
700 new DecimalType("2"), null, bundleContext);
702 assertSingleStateUpdate(dataHandler, CHANNEL_LAST_WRITE_SUCCESS, is(notNullValue(State.class)));
703 assertSingleStateUpdate(dataHandler, CHANNEL_LAST_WRITE_ERROR, is(nullValue(State.class)));
704 assertThat(writeRequests.size(), is(equalTo(2)));
706 ModbusWriteRequestBlueprint writeRequest = writeRequests.get(0);
707 assertThat(writeRequest.getFunctionCode(), is(equalTo(ModbusWriteFunctionCode.WRITE_MULTIPLE_REGISTERS)));
708 assertThat(writeRequest.getReference(), is(equalTo(5412)));
709 assertThat(((ModbusWriteRegisterRequestBlueprint) writeRequest).getRegisters().size(), is(equalTo(3)));
710 assertThat(((ModbusWriteRegisterRequestBlueprint) writeRequest).getRegisters().getRegister(0).getValue(),
712 assertThat(((ModbusWriteRegisterRequestBlueprint) writeRequest).getRegisters().getRegister(1).getValue(),
714 assertThat(((ModbusWriteRegisterRequestBlueprint) writeRequest).getRegisters().getRegister(2).getValue(),
718 ModbusWriteRequestBlueprint writeRequest = writeRequests.get(1);
719 assertThat(writeRequest.getFunctionCode(), is(equalTo(ModbusWriteFunctionCode.WRITE_SINGLE_REGISTER)));
720 assertThat(writeRequest.getReference(), is(equalTo(555)));
721 assertThat(((ModbusWriteRegisterRequestBlueprint) writeRequest).getRegisters().size(), is(equalTo(1)));
722 assertThat(((ModbusWriteRegisterRequestBlueprint) writeRequest).getRegisters().getRegister(0).getValue(),
727 private void testValueTypeGeneric(ModbusReadFunctionCode functionCode, ValueType valueType,
728 ThingStatus expectedStatus) {
729 ModbusSlaveEndpoint endpoint = new ModbusTCPSlaveEndpoint("thisishost", 502);
731 // Minimally mocked request
732 ModbusReadRequestBlueprint request = Mockito.mock(ModbusReadRequestBlueprint.class);
733 doReturn(3).when(request).getDataLength();
734 doReturn(functionCode).when(request).getFunctionCode();
736 PollTask task = Mockito.mock(PollTask.class);
737 doReturn(endpoint).when(task).getEndpoint();
738 doReturn(request).when(task).getRequest();
740 Bridge poller = createPollerMock("poller1", task);
742 Configuration dataConfig = new Configuration();
743 dataConfig.put("readStart", "1");
744 dataConfig.put("readTransform", "default");
745 dataConfig.put("readValueType", valueType.getConfigValue());
746 ModbusDataThingHandler dataHandler = createDataHandler("data1", poller,
747 builder -> builder.withConfiguration(dataConfig));
748 assertThat(dataHandler.getThing().getStatus(), is(equalTo(expectedStatus)));
752 public void testCoilDoesNotAcceptFloat32ValueType() {
753 testValueTypeGeneric(ModbusReadFunctionCode.READ_COILS, ModbusConstants.ValueType.FLOAT32, ThingStatus.OFFLINE);
757 public void testCoilAcceptsBitValueType() {
758 testValueTypeGeneric(ModbusReadFunctionCode.READ_COILS, ModbusConstants.ValueType.BIT, ThingStatus.ONLINE);
762 public void testDiscreteInputDoesNotAcceptFloat32ValueType() {
763 testValueTypeGeneric(ModbusReadFunctionCode.READ_INPUT_DISCRETES, ModbusConstants.ValueType.FLOAT32,
764 ThingStatus.OFFLINE);
768 public void testDiscreteInputAcceptsBitValueType() {
769 testValueTypeGeneric(ModbusReadFunctionCode.READ_INPUT_DISCRETES, ModbusConstants.ValueType.BIT,
774 public void testRefreshOnData() throws InterruptedException {
775 ModbusReadFunctionCode functionCode = ModbusReadFunctionCode.READ_COILS;
777 ModbusSlaveEndpoint endpoint = new ModbusTCPSlaveEndpoint("thisishost", 502);
781 // Minimally mocked request
782 ModbusReadRequestBlueprint request = Mockito.mock(ModbusReadRequestBlueprint.class);
783 doReturn(pollLength).when(request).getDataLength();
784 doReturn(functionCode).when(request).getFunctionCode();
786 PollTask task = Mockito.mock(PollTask.class);
787 doReturn(endpoint).when(task).getEndpoint();
788 doReturn(request).when(task).getRequest();
790 Bridge poller = createPollerMock("poller1", task);
792 Configuration dataConfig = new Configuration();
793 dataConfig.put("readStart", "0");
794 dataConfig.put("readTransform", "default");
795 dataConfig.put("readValueType", "bit");
797 String thingId = "read1";
799 ModbusDataThingHandler dataHandler = createDataHandler(thingId, poller,
800 builder -> builder.withConfiguration(dataConfig), bundleContext);
801 assertThat(dataHandler.getThing().getStatus(), is(equalTo(ThingStatus.ONLINE)));
803 verify(comms, never()).submitOneTimePoll(eq(request), notNull(), notNull());
804 // Reset initial REFRESH commands to data thing channels from the Core
805 reset(poller.getHandler());
806 dataHandler.handleCommand(Mockito.mock(ChannelUID.class), RefreshType.REFRESH);
808 // data handler asynchronously calls the poller.refresh() -- it might take some time
809 // We check that refresh is finally called
810 waitForAssert(() -> verify((ModbusPollerThingHandler) poller.getHandler()).refresh(), 2500, 50);
815 * @param pollerFunctionCode poller function code. Use null if you want to have data thing direct child of endpoint
817 * @param config thing config
818 * @param statusConsumer assertion method for data thingstatus
820 private void testInitGeneric(ModbusReadFunctionCode pollerFunctionCode, Configuration config,
821 Consumer<ThingStatusInfo> statusConsumer) {
825 if (pollerFunctionCode == null) {
826 parent = createTcpMock();
827 ThingHandler foo = parent.getHandler();
830 ModbusSlaveEndpoint endpoint = new ModbusTCPSlaveEndpoint("thisishost", 502);
832 // Minimally mocked request
833 ModbusReadRequestBlueprint request = Mockito.mock(ModbusReadRequestBlueprint.class);
834 doReturn(pollLength).when(request).getDataLength();
835 doReturn(pollerFunctionCode).when(request).getFunctionCode();
837 PollTask task = Mockito.mock(PollTask.class);
838 doReturn(endpoint).when(task).getEndpoint();
839 doReturn(request).when(task).getRequest();
841 parent = createPollerMock("poller1", task);
844 String thingId = "read1";
846 ModbusDataThingHandler dataHandler = createDataHandler(thingId, parent,
847 builder -> builder.withConfiguration(config), bundleContext);
849 statusConsumer.accept(dataHandler.getThing().getStatusInfo());
853 public void testReadOnlyData() {
854 Configuration dataConfig = new Configuration();
855 dataConfig.put("readStart", "0");
856 dataConfig.put("readValueType", "bit");
857 testInitGeneric(ModbusReadFunctionCode.READ_COILS, dataConfig,
858 status -> assertThat(status.getStatus(), is(equalTo(ThingStatus.ONLINE))));
862 * readValueType=bit should be assumed with coils, so it's ok to skip it
865 public void testReadOnlyDataMissingValueTypeWithCoils() {
866 Configuration dataConfig = new Configuration();
867 dataConfig.put("readStart", "0");
868 // missing value type
869 testInitGeneric(ModbusReadFunctionCode.READ_COILS, dataConfig,
870 status -> assertThat(status.getStatus(), is(equalTo(ThingStatus.ONLINE))));
874 public void testReadOnlyDataInvalidValueType() {
875 Configuration dataConfig = new Configuration();
876 dataConfig.put("readStart", "0");
877 dataConfig.put("readValueType", "foobar");
878 testInitGeneric(ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS, dataConfig, status -> {
879 assertThat(status.getStatus(), is(equalTo(ThingStatus.OFFLINE)));
880 assertThat(status.getStatusDetail(), is(equalTo(ThingStatusDetail.CONFIGURATION_ERROR)));
885 * We do not assume value type with registers, not ok to skip it
888 public void testReadOnlyDataMissingValueTypeWithRegisters() {
889 Configuration dataConfig = new Configuration();
890 dataConfig.put("readStart", "0");
891 testInitGeneric(ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS, dataConfig, status -> {
892 assertThat(status.getStatus(), is(equalTo(ThingStatus.OFFLINE)));
893 assertThat(status.getStatusDetail(), is(equalTo(ThingStatusDetail.CONFIGURATION_ERROR)));
898 public void testWriteOnlyData() {
899 Configuration dataConfig = new Configuration();
900 dataConfig.put("writeStart", "0");
901 dataConfig.put("writeValueType", "bit");
902 dataConfig.put("writeType", "coil");
903 testInitGeneric(ModbusReadFunctionCode.READ_COILS, dataConfig,
904 status -> assertThat(status.getStatus(), is(equalTo(ThingStatus.ONLINE))));
908 public void testWriteHoldingInt16Data() {
909 Configuration dataConfig = new Configuration();
910 dataConfig.put("writeStart", "0");
911 dataConfig.put("writeValueType", "int16");
912 dataConfig.put("writeType", "holding");
913 testInitGeneric(ModbusReadFunctionCode.READ_COILS, dataConfig,
914 status -> assertThat(status.getStatus(), is(equalTo(ThingStatus.ONLINE))));
918 public void testWriteHoldingInt8Data() {
919 Configuration dataConfig = new Configuration();
920 dataConfig.put("writeStart", "0");
921 dataConfig.put("writeValueType", "int8");
922 dataConfig.put("writeType", "holding");
923 testInitGeneric(null, dataConfig, status -> {
924 assertThat(status.getStatus(), is(equalTo(ThingStatus.OFFLINE)));
925 assertThat(status.getStatusDetail(), is(equalTo(ThingStatusDetail.CONFIGURATION_ERROR)));
930 public void testWriteHoldingBitData() {
931 Configuration dataConfig = new Configuration();
932 dataConfig.put("writeStart", "0");
933 dataConfig.put("writeValueType", "bit");
934 dataConfig.put("writeType", "holding");
935 testInitGeneric(null, dataConfig, status -> {
936 assertThat(status.getStatus(), is(equalTo(ThingStatus.OFFLINE)));
937 assertThat(status.getStatusDetail(), is(equalTo(ThingStatusDetail.CONFIGURATION_ERROR)));
942 public void testWriteOnlyDataChildOfEndpoint() {
943 Configuration dataConfig = new Configuration();
944 dataConfig.put("writeStart", "0");
945 dataConfig.put("writeValueType", "bit");
946 dataConfig.put("writeType", "coil");
947 testInitGeneric(null, dataConfig, status -> assertThat(status.getStatus(), is(equalTo(ThingStatus.ONLINE))));
951 public void testWriteOnlyDataMissingOneParameter() {
952 Configuration dataConfig = new Configuration();
953 dataConfig.put("writeStart", "0");
954 dataConfig.put("writeValueType", "bit");
955 // missing writeType --> error
956 testInitGeneric(ModbusReadFunctionCode.READ_COILS, dataConfig, status -> {
957 assertThat(status.getStatus(), is(equalTo(ThingStatus.OFFLINE)));
958 assertThat(status.getStatusDetail(), is(equalTo(ThingStatusDetail.CONFIGURATION_ERROR)));
959 assertThat(status.getDescription(), is(not(equalTo(null))));
964 * OK to omit writeValueType with coils since bit is assumed
967 public void testWriteOnlyDataMissingValueTypeWithCoilParameter() {
968 Configuration dataConfig = new Configuration();
969 dataConfig.put("writeStart", "0");
970 dataConfig.put("writeType", "coil");
971 testInitGeneric(ModbusReadFunctionCode.READ_COILS, dataConfig,
972 status -> assertThat(status.getStatus(), is(equalTo(ThingStatus.ONLINE))));
976 public void testWriteOnlyIllegalValueType() {
977 Configuration dataConfig = new Configuration();
978 dataConfig.put("writeStart", "0");
979 dataConfig.put("writeType", "coil");
980 dataConfig.put("writeValueType", "foobar");
981 testInitGeneric(ModbusReadFunctionCode.READ_COILS, dataConfig, status -> {
982 assertThat(status.getStatus(), is(equalTo(ThingStatus.OFFLINE)));
983 assertThat(status.getStatusDetail(), is(equalTo(ThingStatusDetail.CONFIGURATION_ERROR)));
988 public void testWriteInvalidType() {
989 Configuration dataConfig = new Configuration();
990 dataConfig.put("writeStart", "0");
991 dataConfig.put("writeType", "foobar");
992 testInitGeneric(ModbusReadFunctionCode.READ_COILS, dataConfig, status -> {
993 assertThat(status.getStatus(), is(equalTo(ThingStatus.OFFLINE)));
994 assertThat(status.getStatusDetail(), is(equalTo(ThingStatusDetail.CONFIGURATION_ERROR)));
999 public void testWriteCoilBadStart() {
1000 Configuration dataConfig = new Configuration();
1001 dataConfig.put("writeStart", "0.4");
1002 dataConfig.put("writeType", "coil");
1003 testInitGeneric(null, dataConfig, status -> {
1004 assertThat(status.getStatus(), is(equalTo(ThingStatus.OFFLINE)));
1005 assertThat(status.getStatusDetail(), is(equalTo(ThingStatusDetail.CONFIGURATION_ERROR)));
1010 public void testWriteHoldingBadStart() {
1011 Configuration dataConfig = new Configuration();
1012 dataConfig.put("writeStart", "0.4");
1013 dataConfig.put("writeType", "holding");
1014 testInitGeneric(null, dataConfig, status -> {
1015 assertThat(status.getStatus(), is(equalTo(ThingStatus.OFFLINE)));
1016 assertThat(status.getStatusDetail(), is(equalTo(ThingStatusDetail.CONFIGURATION_ERROR)));
1021 public void testReadHoldingBadStart() {
1022 Configuration dataConfig = new Configuration();
1023 dataConfig.put("readStart", "0.0");
1024 dataConfig.put("readValueType", "int16");
1025 testInitGeneric(ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS, dataConfig, status -> {
1026 assertThat(status.getStatus(), is(equalTo(ThingStatus.OFFLINE)));
1027 assertThat(status.getStatusDetail(), is(equalTo(ThingStatusDetail.CONFIGURATION_ERROR)));
1032 public void testReadHoldingBadStart2() {
1033 Configuration dataConfig = new Configuration();
1034 dataConfig.put("readStart", "0.0");
1035 dataConfig.put("readValueType", "bit");
1036 testInitGeneric(ModbusReadFunctionCode.READ_COILS, dataConfig, status -> {
1037 assertThat(status.getStatus(), is(equalTo(ThingStatus.OFFLINE)));
1038 assertThat(status.getStatusDetail(), is(equalTo(ThingStatusDetail.CONFIGURATION_ERROR)));
1043 public void testReadHoldingOKStart() {
1044 Configuration dataConfig = new Configuration();
1045 dataConfig.put("readStart", "0.0");
1046 dataConfig.put("readType", "holding");
1047 dataConfig.put("readValueType", "bit");
1048 testInitGeneric(ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS, dataConfig,
1049 status -> assertThat(status.getStatus(), is(equalTo(ThingStatus.ONLINE))));
1053 public void testReadValueTypeIllegal() {
1054 Configuration dataConfig = new Configuration();
1055 dataConfig.put("readStart", "0.0");
1056 dataConfig.put("readType", "holding");
1057 dataConfig.put("readValueType", "foobar");
1058 testInitGeneric(ModbusReadFunctionCode.READ_COILS, dataConfig, status -> {
1059 assertThat(status.getStatus(), is(equalTo(ThingStatus.OFFLINE)));
1060 assertThat(status.getStatusDetail(), is(equalTo(ThingStatusDetail.CONFIGURATION_ERROR)));
1065 public void testWriteOnlyTransform() {
1066 Configuration dataConfig = new Configuration();
1067 // no need to have start, JSON output of transformation defines everything
1068 dataConfig.put("writeTransform", "JS(myJsonTransform.js)");
1069 testInitGeneric(null, dataConfig, status -> assertThat(status.getStatus(), is(equalTo(ThingStatus.ONLINE))));
1073 public void testWriteTransformAndStart() {
1074 Configuration dataConfig = new Configuration();
1075 // It's illegal to have start and transform. Just have transform or have all
1076 dataConfig.put("writeStart", "3");
1077 dataConfig.put("writeTransform", "JS(myJsonTransform.js)");
1078 testInitGeneric(ModbusReadFunctionCode.READ_COILS, dataConfig, status -> {
1079 assertThat(status.getStatus(), is(equalTo(ThingStatus.OFFLINE)));
1080 assertThat(status.getStatusDetail(), is(equalTo(ThingStatusDetail.CONFIGURATION_ERROR)));
1085 public void testWriteTransformAndNecessary() {
1086 Configuration dataConfig = new Configuration();
1087 // It's illegal to have start and transform. Just have transform or have all
1088 dataConfig.put("writeStart", "3");
1089 dataConfig.put("writeType", "holding");
1090 dataConfig.put("writeValueType", "int16");
1091 dataConfig.put("writeTransform", "JS(myJsonTransform.js)");
1092 testInitGeneric(null, dataConfig, status -> assertThat(status.getStatus(), is(equalTo(ThingStatus.ONLINE))));