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.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.*;
24 import java.util.Map.Entry;
25 import java.util.concurrent.ScheduledFuture;
26 import java.util.function.Consumer;
27 import java.util.function.Function;
29 import org.apache.commons.lang.StringUtils;
30 import org.hamcrest.Matcher;
31 import org.junit.jupiter.api.AfterEach;
32 import org.junit.jupiter.api.Test;
33 import org.mockito.Mockito;
34 import org.openhab.binding.modbus.handler.EndpointNotInitializedException;
35 import org.openhab.binding.modbus.handler.ModbusPollerThingHandler;
36 import org.openhab.binding.modbus.internal.handler.ModbusDataThingHandler;
37 import org.openhab.binding.modbus.internal.handler.ModbusTcpThingHandler;
38 import org.openhab.core.config.core.Configuration;
39 import org.openhab.core.items.GenericItem;
40 import org.openhab.core.items.Item;
41 import org.openhab.core.library.items.DateTimeItem;
42 import org.openhab.core.library.types.DecimalType;
43 import org.openhab.core.library.types.OnOffType;
44 import org.openhab.core.library.types.OpenClosedType;
45 import org.openhab.core.library.types.StringType;
46 import org.openhab.core.thing.Bridge;
47 import org.openhab.core.thing.ChannelUID;
48 import org.openhab.core.thing.Thing;
49 import org.openhab.core.thing.ThingStatus;
50 import org.openhab.core.thing.ThingStatusDetail;
51 import org.openhab.core.thing.ThingStatusInfo;
52 import org.openhab.core.thing.ThingUID;
53 import org.openhab.core.thing.binding.ThingHandler;
54 import org.openhab.core.thing.binding.builder.BridgeBuilder;
55 import org.openhab.core.thing.binding.builder.ChannelBuilder;
56 import org.openhab.core.thing.binding.builder.ThingBuilder;
57 import org.openhab.core.transform.TransformationException;
58 import org.openhab.core.transform.TransformationService;
59 import org.openhab.core.types.Command;
60 import org.openhab.core.types.RefreshType;
61 import org.openhab.core.types.State;
62 import org.openhab.core.types.UnDefType;
63 import org.openhab.io.transport.modbus.AsyncModbusFailure;
64 import org.openhab.io.transport.modbus.AsyncModbusReadResult;
65 import org.openhab.io.transport.modbus.AsyncModbusWriteResult;
66 import org.openhab.io.transport.modbus.BitArray;
67 import org.openhab.io.transport.modbus.ModbusConstants;
68 import org.openhab.io.transport.modbus.ModbusConstants.ValueType;
69 import org.openhab.io.transport.modbus.ModbusReadFunctionCode;
70 import org.openhab.io.transport.modbus.ModbusReadRequestBlueprint;
71 import org.openhab.io.transport.modbus.ModbusRegister;
72 import org.openhab.io.transport.modbus.ModbusRegisterArray;
73 import org.openhab.io.transport.modbus.ModbusResponse;
74 import org.openhab.io.transport.modbus.ModbusWriteCoilRequestBlueprint;
75 import org.openhab.io.transport.modbus.ModbusWriteFunctionCode;
76 import org.openhab.io.transport.modbus.ModbusWriteRegisterRequestBlueprint;
77 import org.openhab.io.transport.modbus.ModbusWriteRequestBlueprint;
78 import org.openhab.io.transport.modbus.PollTask;
79 import org.openhab.io.transport.modbus.endpoint.ModbusSlaveEndpoint;
80 import org.openhab.io.transport.modbus.endpoint.ModbusTCPSlaveEndpoint;
81 import org.osgi.framework.BundleContext;
82 import org.osgi.framework.InvalidSyntaxException;
85 * @author Sami Salonen - Initial contribution
87 public class ModbusDataHandlerTest extends AbstractModbusOSGiTest {
89 private final class MultiplyTransformation implements TransformationService {
91 public String transform(String function, String source) throws TransformationException {
92 return String.valueOf(Integer.parseInt(function) * Integer.parseInt(source));
96 private static final Map<String, String> CHANNEL_TO_ACCEPTED_TYPE = new HashMap<>();
98 CHANNEL_TO_ACCEPTED_TYPE.put(CHANNEL_SWITCH, "Switch");
99 CHANNEL_TO_ACCEPTED_TYPE.put(CHANNEL_CONTACT, "Contact");
100 CHANNEL_TO_ACCEPTED_TYPE.put(CHANNEL_DATETIME, "DateTime");
101 CHANNEL_TO_ACCEPTED_TYPE.put(CHANNEL_DIMMER, "Dimmer");
102 CHANNEL_TO_ACCEPTED_TYPE.put(CHANNEL_NUMBER, "Number");
103 CHANNEL_TO_ACCEPTED_TYPE.put(CHANNEL_STRING, "String");
104 CHANNEL_TO_ACCEPTED_TYPE.put(CHANNEL_ROLLERSHUTTER, "Rollershutter");
105 CHANNEL_TO_ACCEPTED_TYPE.put(CHANNEL_LAST_READ_SUCCESS, "DateTime");
106 CHANNEL_TO_ACCEPTED_TYPE.put(CHANNEL_LAST_WRITE_SUCCESS, "DateTime");
107 CHANNEL_TO_ACCEPTED_TYPE.put(CHANNEL_LAST_WRITE_ERROR, "DateTime");
108 CHANNEL_TO_ACCEPTED_TYPE.put(CHANNEL_LAST_READ_ERROR, "DateTime");
110 private List<ModbusWriteRequestBlueprint> writeRequests = new ArrayList<>();
113 public void tearDown() {
114 writeRequests.clear();
117 private void captureModbusWrites() {
118 Mockito.when(comms.submitOneTimeWrite(any(), any(), any())).then(invocation -> {
119 ModbusWriteRequestBlueprint task = (ModbusWriteRequestBlueprint) invocation.getArgument(0);
120 writeRequests.add(task);
121 return Mockito.mock(ScheduledFuture.class);
125 private Bridge createPollerMock(String pollerId, PollTask task) {
127 ThingUID thingUID = new ThingUID(THING_TYPE_MODBUS_POLLER, pollerId);
128 BridgeBuilder builder = BridgeBuilder.create(THING_TYPE_MODBUS_POLLER, thingUID)
129 .withLabel("label for " + pollerId);
130 for (Entry<String, String> entry : CHANNEL_TO_ACCEPTED_TYPE.entrySet()) {
131 String channelId = entry.getKey();
132 String channelAcceptedType = entry.getValue();
133 builder = builder.withChannel(
134 ChannelBuilder.create(new ChannelUID(thingUID, channelId), channelAcceptedType).build());
136 poller = builder.build();
137 poller.setStatusInfo(new ThingStatusInfo(ThingStatus.ONLINE, ThingStatusDetail.NONE, ""));
139 ModbusPollerThingHandler mockHandler = Mockito.mock(ModbusPollerThingHandler.class);
140 doReturn(task.getRequest()).when(mockHandler).getRequest();
141 assert comms != null;
142 doReturn(comms).when(mockHandler).getCommunicationInterface();
143 doReturn(task.getEndpoint()).when(comms).getEndpoint();
144 poller.setHandler(mockHandler);
145 assertSame(poller.getHandler(), mockHandler);
146 assertSame(((ModbusPollerThingHandler) poller.getHandler()).getCommunicationInterface().getEndpoint(),
148 assertSame(((ModbusPollerThingHandler) poller.getHandler()).getRequest(), task.getRequest());
154 private Bridge createTcpMock() {
155 ModbusSlaveEndpoint endpoint = new ModbusTCPSlaveEndpoint("thisishost", 502);
156 Bridge tcpBridge = ModbusPollerThingHandlerTest.createTcpThingBuilder("tcp1").build();
157 ModbusTcpThingHandler tcpThingHandler = Mockito.mock(ModbusTcpThingHandler.class);
158 tcpBridge.setStatusInfo(new ThingStatusInfo(ThingStatus.ONLINE, ThingStatusDetail.NONE, ""));
159 tcpBridge.setHandler(tcpThingHandler);
160 doReturn(comms).when(tcpThingHandler).getCommunicationInterface();
162 doReturn(0).when(tcpThingHandler).getSlaveId();
163 } catch (EndpointNotInitializedException e) {
164 // not raised -- we are mocking return value only, not actually calling the method
165 throw new IllegalStateException();
167 tcpThingHandler.initialize();
168 assertThat(tcpBridge.getStatus(), is(equalTo(ThingStatus.ONLINE)));
172 private ModbusDataThingHandler createDataHandler(String id, Bridge bridge,
173 Function<ThingBuilder, ThingBuilder> builderConfigurator) {
174 return createDataHandler(id, bridge, builderConfigurator, null);
177 private ModbusDataThingHandler createDataHandler(String id, Bridge bridge,
178 Function<ThingBuilder, ThingBuilder> builderConfigurator, BundleContext context) {
179 return createDataHandler(id, bridge, builderConfigurator, context, true);
182 private ModbusDataThingHandler createDataHandler(String id, Bridge bridge,
183 Function<ThingBuilder, ThingBuilder> builderConfigurator, BundleContext context,
184 boolean autoCreateItemsAndLinkToChannels) {
185 ThingUID thingUID = new ThingUID(THING_TYPE_MODBUS_DATA, id);
186 ThingBuilder builder = ThingBuilder.create(THING_TYPE_MODBUS_DATA, thingUID).withLabel("label for " + id);
187 Map<String, ChannelUID> toBeLinked = new HashMap<>();
188 for (Entry<String, String> entry : CHANNEL_TO_ACCEPTED_TYPE.entrySet()) {
189 String channelId = entry.getKey();
190 String channelAcceptedType = entry.getValue();
191 ChannelUID channelUID = new ChannelUID(thingUID, channelId);
192 builder = builder.withChannel(ChannelBuilder.create(channelUID, channelAcceptedType).build());
194 if (autoCreateItemsAndLinkToChannels) {
195 // Create item of correct type and link it to channel
196 String itemName = getItemName(channelUID);
197 final GenericItem item;
198 if (channelId.startsWith("last") || channelId.equals("datetime")) {
199 item = new DateTimeItem(itemName);
201 item = coreItemFactory.createItem(StringUtils.capitalize(channelId), itemName);
203 assertThat(String.format("Could not determine correct item type for %s", channelId), item,
206 Objects.requireNonNull(item);
208 toBeLinked.put(itemName, channelUID);
211 if (builderConfigurator != null) {
212 builder = builderConfigurator.apply(builder);
215 Thing dataThing = builder.withBridge(bridge.getUID()).build();
218 // Link after the things and items have been created
219 for (Entry<String, ChannelUID> entry : toBeLinked.entrySet()) {
220 linkItem(entry.getKey(), entry.getValue());
222 return (ModbusDataThingHandler) dataThing.getHandler();
225 private String getItemName(ChannelUID channelUID) {
226 return channelUID.toString().replace(':', '_') + "_item";
229 private void assertSingleStateUpdate(ModbusDataThingHandler handler, String channel, Matcher<State> matcher) {
230 waitForAssert(() -> {
231 ChannelUID channelUID = new ChannelUID(handler.getThing().getUID(), channel);
232 String itemName = getItemName(channelUID);
233 Item item = itemRegistry.get(itemName);
234 assertThat(String.format("Item %s is not available from item registry", itemName), item,
237 List<State> updates = getStateUpdates(itemName);
238 if (updates != null) {
240 String.format("Many updates found, expected one: %s", Arrays.deepToString(updates.toArray())),
241 updates.size(), is(equalTo(1)));
243 State state = updates == null ? null : updates.get(0);
244 assertThat(String.format("%s %s, state %s of type %s", item.getClass().getSimpleName(), itemName, state,
245 state == null ? null : state.getClass().getSimpleName()), state, is(matcher));
249 private void assertSingleStateUpdate(ModbusDataThingHandler handler, String channel, State state) {
250 assertSingleStateUpdate(handler, channel, is(equalTo(state)));
253 private void testOutOfBoundsGeneric(int pollStart, int pollLength, String start,
254 ModbusReadFunctionCode functionCode, ValueType valueType, ThingStatus expectedStatus) {
255 testOutOfBoundsGeneric(pollStart, pollLength, start, functionCode, valueType, expectedStatus, null);
258 private void testOutOfBoundsGeneric(int pollStart, int pollLength, String start,
259 ModbusReadFunctionCode functionCode, ValueType valueType, ThingStatus expectedStatus,
260 BundleContext context) {
261 ModbusSlaveEndpoint endpoint = new ModbusTCPSlaveEndpoint("thisishost", 502);
263 // Minimally mocked request
264 ModbusReadRequestBlueprint request = Mockito.mock(ModbusReadRequestBlueprint.class);
265 doReturn(pollStart).when(request).getReference();
266 doReturn(pollLength).when(request).getDataLength();
267 doReturn(functionCode).when(request).getFunctionCode();
269 PollTask task = Mockito.mock(PollTask.class);
270 doReturn(endpoint).when(task).getEndpoint();
271 doReturn(request).when(task).getRequest();
273 Bridge pollerThing = createPollerMock("poller1", task);
275 Configuration dataConfig = new Configuration();
276 dataConfig.put("readStart", start);
277 dataConfig.put("readTransform", "default");
278 dataConfig.put("readValueType", valueType.getConfigValue());
279 ModbusDataThingHandler dataHandler = createDataHandler("data1", pollerThing,
280 builder -> builder.withConfiguration(dataConfig), context);
281 assertThat(dataHandler.getThing().getStatusInfo().getDescription(), dataHandler.getThing().getStatus(),
282 is(equalTo(expectedStatus)));
286 public void testInitCoilsOutOfIndex() {
287 testOutOfBoundsGeneric(4, 3, "8", ModbusReadFunctionCode.READ_COILS, ModbusConstants.ValueType.BIT,
288 ThingStatus.OFFLINE);
292 public void testInitCoilsOutOfIndex2() {
293 // Reading coils 4, 5, 6. Coil 7 is out of bounds
294 testOutOfBoundsGeneric(4, 3, "7", ModbusReadFunctionCode.READ_COILS, ModbusConstants.ValueType.BIT,
295 ThingStatus.OFFLINE);
299 public void testInitCoilsOK() {
300 // Reading coils 4, 5, 6. Coil 6 is OK
301 testOutOfBoundsGeneric(4, 3, "6", ModbusReadFunctionCode.READ_COILS, ModbusConstants.ValueType.BIT,
306 public void testInitRegistersWithBitOutOfIndex() {
307 testOutOfBoundsGeneric(4, 3, "8.0", ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS,
308 ModbusConstants.ValueType.BIT, ThingStatus.OFFLINE);
312 public void testInitRegistersWithBitOutOfIndex2() {
313 testOutOfBoundsGeneric(4, 3, "7.16", ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS,
314 ModbusConstants.ValueType.BIT, ThingStatus.OFFLINE);
318 public void testInitRegistersWithBitOK() {
319 testOutOfBoundsGeneric(4, 3, "6.0", ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS,
320 ModbusConstants.ValueType.BIT, ThingStatus.ONLINE);
324 public void testInitRegistersWithBitOK2() {
325 testOutOfBoundsGeneric(4, 3, "6.15", ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS,
326 ModbusConstants.ValueType.BIT, ThingStatus.ONLINE);
330 public void testInitRegistersWithInt8OutOfIndex() {
331 testOutOfBoundsGeneric(4, 3, "8.0", ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS,
332 ModbusConstants.ValueType.INT8, ThingStatus.OFFLINE);
336 public void testInitRegistersWithInt8OutOfIndex2() {
337 testOutOfBoundsGeneric(4, 3, "7.2", ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS,
338 ModbusConstants.ValueType.INT8, ThingStatus.OFFLINE);
342 public void testInitRegistersWithInt8OK() {
343 testOutOfBoundsGeneric(4, 3, "6.0", ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS,
344 ModbusConstants.ValueType.INT8, ThingStatus.ONLINE);
348 public void testInitRegistersWithInt8OK2() {
349 testOutOfBoundsGeneric(4, 3, "6.1", ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS,
350 ModbusConstants.ValueType.INT8, ThingStatus.ONLINE);
354 public void testInitRegistersWithInt16OK() {
355 // Poller reading registers 4, 5, 6. Register 6 is OK
356 testOutOfBoundsGeneric(4, 3, "6", ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS,
357 ModbusConstants.ValueType.INT16, ThingStatus.ONLINE);
361 public void testInitRegistersWithInt16OutOfBounds() {
362 // Poller reading registers 4, 5, 6. Register 7 is out-of-bounds
363 testOutOfBoundsGeneric(4, 3, "7", ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS,
364 ModbusConstants.ValueType.INT16, ThingStatus.OFFLINE);
368 public void testInitRegistersWithInt16OutOfBounds2() {
369 testOutOfBoundsGeneric(4, 3, "8", ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS,
370 ModbusConstants.ValueType.INT16, ThingStatus.OFFLINE);
374 public void testInitRegistersWithInt16NoDecimalFormatAllowed() {
375 testOutOfBoundsGeneric(4, 3, "7.0", ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS,
376 ModbusConstants.ValueType.INT16, ThingStatus.OFFLINE);
380 public void testInitRegistersWithInt32OK() {
381 testOutOfBoundsGeneric(4, 3, "5", ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS,
382 ModbusConstants.ValueType.INT32, ThingStatus.ONLINE);
386 public void testInitRegistersWithInt32OutOfBounds() {
387 testOutOfBoundsGeneric(4, 3, "6", ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS,
388 ModbusConstants.ValueType.INT32, ThingStatus.OFFLINE);
392 public void testInitRegistersWithInt32AtTheEdge() {
393 testOutOfBoundsGeneric(4, 3, "5", ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS,
394 ModbusConstants.ValueType.INT32, ThingStatus.ONLINE);
397 private ModbusDataThingHandler testReadHandlingGeneric(ModbusReadFunctionCode functionCode, String start,
398 String transform, ValueType valueType, BitArray bits, ModbusRegisterArray registers, Exception error) {
399 return testReadHandlingGeneric(functionCode, start, transform, valueType, bits, registers, error, null);
402 private ModbusDataThingHandler testReadHandlingGeneric(ModbusReadFunctionCode functionCode, String start,
403 String transform, ValueType valueType, BitArray bits, ModbusRegisterArray registers, Exception error,
404 BundleContext context) {
405 return testReadHandlingGeneric(functionCode, start, transform, valueType, bits, registers, error, context,
409 @SuppressWarnings({ "null" })
410 private ModbusDataThingHandler testReadHandlingGeneric(ModbusReadFunctionCode functionCode, String start,
411 String transform, ValueType valueType, BitArray bits, ModbusRegisterArray registers, Exception error,
412 BundleContext context, boolean autoCreateItemsAndLinkToChannels) {
413 ModbusSlaveEndpoint endpoint = new ModbusTCPSlaveEndpoint("thisishost", 502);
417 // Minimally mocked request
418 ModbusReadRequestBlueprint request = Mockito.mock(ModbusReadRequestBlueprint.class);
419 doReturn(pollLength).when(request).getDataLength();
420 doReturn(functionCode).when(request).getFunctionCode();
422 PollTask task = Mockito.mock(PollTask.class);
423 doReturn(endpoint).when(task).getEndpoint();
424 doReturn(request).when(task).getRequest();
426 Bridge poller = createPollerMock("poller1", task);
428 Configuration dataConfig = new Configuration();
429 dataConfig.put("readStart", start);
430 dataConfig.put("readTransform", transform);
431 dataConfig.put("readValueType", valueType.getConfigValue());
433 String thingId = "read1";
434 ModbusDataThingHandler dataHandler = createDataHandler(thingId, poller,
435 builder -> builder.withConfiguration(dataConfig), context, autoCreateItemsAndLinkToChannels);
437 assertThat(dataHandler.getThing().getStatus(), is(equalTo(ThingStatus.ONLINE)));
441 assertNull(registers);
443 AsyncModbusReadResult result = new AsyncModbusReadResult(request, bits);
444 dataHandler.onReadResult(result);
445 } else if (registers != null) {
448 AsyncModbusReadResult result = new AsyncModbusReadResult(request, registers);
449 dataHandler.onReadResult(result);
452 assertNull(registers);
453 assertNotNull(error);
454 AsyncModbusFailure<ModbusReadRequestBlueprint> result = new AsyncModbusFailure<ModbusReadRequestBlueprint>(
456 dataHandler.handleReadError(result);
461 @SuppressWarnings({ "null" })
462 private ModbusDataThingHandler testWriteHandlingGeneric(String start, String transform, ValueType valueType,
463 String writeType, ModbusWriteFunctionCode successFC, String channel, Command command, Exception error,
464 BundleContext context) {
465 ModbusSlaveEndpoint endpoint = new ModbusTCPSlaveEndpoint("thisishost", 502);
467 // Minimally mocked request
468 ModbusReadRequestBlueprint request = Mockito.mock(ModbusReadRequestBlueprint.class);
470 PollTask task = Mockito.mock(PollTask.class);
471 doReturn(endpoint).when(task).getEndpoint();
472 doReturn(request).when(task).getRequest();
474 Bridge poller = createPollerMock("poller1", task);
476 Configuration dataConfig = new Configuration();
477 dataConfig.put("readStart", "");
478 dataConfig.put("writeStart", start);
479 dataConfig.put("writeTransform", transform);
480 dataConfig.put("writeValueType", valueType.getConfigValue());
481 dataConfig.put("writeType", writeType);
483 String thingId = "write";
485 ModbusDataThingHandler dataHandler = createDataHandler(thingId, poller,
486 builder -> builder.withConfiguration(dataConfig), context);
488 assertThat(dataHandler.getThing().getStatus(), is(equalTo(ThingStatus.ONLINE)));
490 dataHandler.handleCommand(new ChannelUID(dataHandler.getThing().getUID(), channel), command);
493 dataHandler.handleReadError(new AsyncModbusFailure<ModbusReadRequestBlueprint>(request, error));
495 ModbusResponse resp = new ModbusResponse() {
498 public int getFunctionCode() {
499 return successFC.getFunctionCode();
503 .onWriteResponse(new AsyncModbusWriteResult(Mockito.mock(ModbusWriteRequestBlueprint.class), resp));
509 public void testOnError() {
510 ModbusDataThingHandler dataHandler = testReadHandlingGeneric(ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS,
511 "0.0", "default", ModbusConstants.ValueType.BIT, null, null, new Exception("fooerror"));
513 assertSingleStateUpdate(dataHandler, CHANNEL_LAST_READ_ERROR, is(notNullValue(State.class)));
517 public void testOnRegistersInt16StaticTransformation() {
518 ModbusDataThingHandler dataHandler = testReadHandlingGeneric(ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS,
519 "0", "-3", ModbusConstants.ValueType.INT16, null,
520 new ModbusRegisterArray(new ModbusRegister[] { new ModbusRegister((byte) 0xff, (byte) 0xfd) }), null);
522 assertSingleStateUpdate(dataHandler, CHANNEL_LAST_READ_SUCCESS, is(notNullValue(State.class)));
523 assertSingleStateUpdate(dataHandler, CHANNEL_LAST_READ_ERROR, is(nullValue(State.class)));
525 // -3 converts to "true"
526 assertSingleStateUpdate(dataHandler, CHANNEL_CONTACT, is(nullValue(State.class)));
527 assertSingleStateUpdate(dataHandler, CHANNEL_SWITCH, is(nullValue(State.class)));
528 assertSingleStateUpdate(dataHandler, CHANNEL_DIMMER, is(nullValue(State.class)));
529 assertSingleStateUpdate(dataHandler, CHANNEL_NUMBER, new DecimalType(-3));
530 // roller shutter fails since -3 is invalid value (not between 0...100)
531 // assertThatStateContains(state, CHANNEL_ROLLERSHUTTER, new PercentType(1));
532 assertSingleStateUpdate(dataHandler, CHANNEL_STRING, new StringType("-3"));
533 // no datetime, conversion not possible without transformation
537 public void testOnRegistersRealTransformation() {
538 mockTransformation("MULTIPLY", new MultiplyTransformation());
539 ModbusDataThingHandler dataHandler = testReadHandlingGeneric(ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS,
540 "0", "MULTIPLY(10)", ModbusConstants.ValueType.INT16, null,
541 new ModbusRegisterArray(new ModbusRegister[] { new ModbusRegister((byte) 0xff, (byte) 0xfd) }), null,
544 assertSingleStateUpdate(dataHandler, CHANNEL_LAST_READ_SUCCESS, is(notNullValue(State.class)));
545 assertSingleStateUpdate(dataHandler, CHANNEL_LAST_READ_ERROR, is(nullValue(State.class)));
547 // transformation output (-30) is not valid for contact or switch
548 assertSingleStateUpdate(dataHandler, CHANNEL_CONTACT, is(nullValue(State.class)));
549 assertSingleStateUpdate(dataHandler, CHANNEL_SWITCH, is(nullValue(State.class)));
550 // -30 is not valid value for Dimmer (PercentType) (not between 0...100)
551 assertSingleStateUpdate(dataHandler, CHANNEL_DIMMER, is(nullValue(State.class)));
552 assertSingleStateUpdate(dataHandler, CHANNEL_NUMBER, new DecimalType(-30));
553 // roller shutter fails since -3 is invalid value (not between 0...100)
554 assertSingleStateUpdate(dataHandler, CHANNEL_ROLLERSHUTTER, is(nullValue(State.class)));
555 assertSingleStateUpdate(dataHandler, CHANNEL_STRING, new StringType("-30"));
556 // no datetime, conversion not possible without transformation
560 public void testOnRegistersNaNFloatInRegisters() throws InvalidSyntaxException {
561 ModbusDataThingHandler dataHandler = testReadHandlingGeneric(ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS,
562 "0", "default", ModbusConstants.ValueType.FLOAT32, null, new ModbusRegisterArray(
563 // equivalent of floating point NaN
564 new ModbusRegister[] { new ModbusRegister((byte) 0x7f, (byte) 0xc0),
565 new ModbusRegister((byte) 0x00, (byte) 0x00) }),
566 null, bundleContext);
568 assertSingleStateUpdate(dataHandler, CHANNEL_LAST_READ_SUCCESS, is(notNullValue(State.class)));
569 assertSingleStateUpdate(dataHandler, CHANNEL_LAST_READ_ERROR, is(nullValue(State.class)));
571 // UNDEF is treated as "boolean true" (OPEN/ON) since it is != 0.
572 assertSingleStateUpdate(dataHandler, CHANNEL_CONTACT, OpenClosedType.OPEN);
573 assertSingleStateUpdate(dataHandler, CHANNEL_SWITCH, OnOffType.ON);
574 assertSingleStateUpdate(dataHandler, CHANNEL_DIMMER, OnOffType.ON);
575 assertSingleStateUpdate(dataHandler, CHANNEL_NUMBER, UnDefType.UNDEF);
576 assertSingleStateUpdate(dataHandler, CHANNEL_ROLLERSHUTTER, UnDefType.UNDEF);
577 assertSingleStateUpdate(dataHandler, CHANNEL_STRING, UnDefType.UNDEF);
581 public void testOnRegistersRealTransformation2() throws InvalidSyntaxException {
582 mockTransformation("ONOFF", new TransformationService() {
585 public String transform(String function, String source) throws TransformationException {
586 return Integer.parseInt(source) != 0 ? "ON" : "OFF";
589 ModbusDataThingHandler dataHandler = testReadHandlingGeneric(ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS,
590 "0", "ONOFF(10)", ModbusConstants.ValueType.INT16, null,
591 new ModbusRegisterArray(new ModbusRegister[] { new ModbusRegister((byte) 0xff, (byte) 0xfd) }), null,
594 assertSingleStateUpdate(dataHandler, CHANNEL_LAST_READ_SUCCESS, is(notNullValue(State.class)));
595 assertSingleStateUpdate(dataHandler, CHANNEL_LAST_READ_ERROR, is(nullValue(State.class)));
597 assertSingleStateUpdate(dataHandler, CHANNEL_CONTACT, is(nullValue(State.class)));
598 assertSingleStateUpdate(dataHandler, CHANNEL_SWITCH, is(equalTo(OnOffType.ON)));
599 assertSingleStateUpdate(dataHandler, CHANNEL_DIMMER, is(equalTo(OnOffType.ON)));
600 assertSingleStateUpdate(dataHandler, CHANNEL_NUMBER, is(nullValue(State.class)));
601 assertSingleStateUpdate(dataHandler, CHANNEL_ROLLERSHUTTER, is(nullValue(State.class)));
602 assertSingleStateUpdate(dataHandler, CHANNEL_STRING, is(equalTo(new StringType("ON"))));
606 public void testWriteRealTransformation() throws InvalidSyntaxException {
607 captureModbusWrites();
608 mockTransformation("MULTIPLY", new MultiplyTransformation());
609 ModbusDataThingHandler dataHandler = testWriteHandlingGeneric("50", "MULTIPLY(10)",
610 ModbusConstants.ValueType.BIT, "coil", ModbusWriteFunctionCode.WRITE_COIL, "number",
611 new DecimalType("2"), null, bundleContext);
613 assertSingleStateUpdate(dataHandler, CHANNEL_LAST_WRITE_SUCCESS, is(notNullValue(State.class)));
614 assertSingleStateUpdate(dataHandler, CHANNEL_LAST_WRITE_ERROR, is(nullValue(State.class)));
615 assertThat(writeRequests.size(), is(equalTo(1)));
616 ModbusWriteRequestBlueprint writeRequest = writeRequests.get(0);
617 assertThat(writeRequest.getFunctionCode(), is(equalTo(ModbusWriteFunctionCode.WRITE_COIL)));
618 assertThat(writeRequest.getReference(), is(equalTo(50)));
619 assertThat(((ModbusWriteCoilRequestBlueprint) writeRequest).getCoils().size(), is(equalTo(1)));
620 // Since transform output is non-zero, it is mapped as "true"
621 assertThat(((ModbusWriteCoilRequestBlueprint) writeRequest).getCoils().getBit(0), is(equalTo(true)));
625 public void testWriteRealTransformation2() throws InvalidSyntaxException {
626 captureModbusWrites();
627 mockTransformation("ZERO", new TransformationService() {
630 public String transform(String function, String source) throws TransformationException {
634 ModbusDataThingHandler dataHandler = testWriteHandlingGeneric("50", "ZERO(foobar)",
635 ModbusConstants.ValueType.BIT, "coil", ModbusWriteFunctionCode.WRITE_COIL, "number",
636 new DecimalType("2"), null, bundleContext);
638 assertSingleStateUpdate(dataHandler, CHANNEL_LAST_WRITE_SUCCESS, is(notNullValue(State.class)));
639 assertSingleStateUpdate(dataHandler, CHANNEL_LAST_WRITE_ERROR, is(nullValue(State.class)));
640 assertThat(writeRequests.size(), is(equalTo(1)));
641 ModbusWriteRequestBlueprint writeRequest = writeRequests.get(0);
642 assertThat(writeRequest.getFunctionCode(), is(equalTo(ModbusWriteFunctionCode.WRITE_COIL)));
643 assertThat(writeRequest.getReference(), is(equalTo(50)));
644 assertThat(((ModbusWriteCoilRequestBlueprint) writeRequest).getCoils().size(), is(equalTo(1)));
645 // Since transform output is zero, it is mapped as "false"
646 assertThat(((ModbusWriteCoilRequestBlueprint) writeRequest).getCoils().getBit(0), is(equalTo(false)));
650 public void testWriteRealTransformation3() throws InvalidSyntaxException {
651 captureModbusWrites();
652 mockTransformation("RANDOM", new TransformationService() {
655 public String transform(String function, String source) throws TransformationException {
659 ModbusDataThingHandler dataHandler = testWriteHandlingGeneric("50", "RANDOM(foobar)",
660 ModbusConstants.ValueType.INT16, "holding", ModbusWriteFunctionCode.WRITE_SINGLE_REGISTER, "number",
661 new DecimalType("2"), null, bundleContext);
663 assertSingleStateUpdate(dataHandler, CHANNEL_LAST_WRITE_SUCCESS, is(notNullValue(State.class)));
664 assertSingleStateUpdate(dataHandler, CHANNEL_LAST_WRITE_ERROR, is(nullValue(State.class)));
665 assertThat(writeRequests.size(), is(equalTo(1)));
666 ModbusWriteRequestBlueprint writeRequest = writeRequests.get(0);
667 assertThat(writeRequest.getFunctionCode(), is(equalTo(ModbusWriteFunctionCode.WRITE_SINGLE_REGISTER)));
668 assertThat(writeRequest.getReference(), is(equalTo(50)));
669 assertThat(((ModbusWriteRegisterRequestBlueprint) writeRequest).getRegisters().size(), is(equalTo(1)));
670 assertThat(((ModbusWriteRegisterRequestBlueprint) writeRequest).getRegisters().getRegister(0).getValue(),
675 public void testWriteRealTransformation4() throws InvalidSyntaxException {
676 captureModbusWrites();
677 mockTransformation("JSON", new TransformationService() {
680 public String transform(String function, String source) throws TransformationException {
682 + "\"functionCode\": 16,"//
683 + "\"address\": 5412,"//
684 + "\"value\": [1, 0, 5]"//
687 + "\"functionCode\": 6,"//
688 + "\"address\": 555,"//
693 ModbusDataThingHandler dataHandler = testWriteHandlingGeneric("50", "JSON(foobar)",
694 ModbusConstants.ValueType.INT16, "holding", ModbusWriteFunctionCode.WRITE_MULTIPLE_REGISTERS, "number",
695 new DecimalType("2"), null, bundleContext);
697 assertSingleStateUpdate(dataHandler, CHANNEL_LAST_WRITE_SUCCESS, is(notNullValue(State.class)));
698 assertSingleStateUpdate(dataHandler, CHANNEL_LAST_WRITE_ERROR, is(nullValue(State.class)));
699 assertThat(writeRequests.size(), is(equalTo(2)));
701 ModbusWriteRequestBlueprint writeRequest = writeRequests.get(0);
702 assertThat(writeRequest.getFunctionCode(), is(equalTo(ModbusWriteFunctionCode.WRITE_MULTIPLE_REGISTERS)));
703 assertThat(writeRequest.getReference(), is(equalTo(5412)));
704 assertThat(((ModbusWriteRegisterRequestBlueprint) writeRequest).getRegisters().size(), is(equalTo(3)));
705 assertThat(((ModbusWriteRegisterRequestBlueprint) writeRequest).getRegisters().getRegister(0).getValue(),
707 assertThat(((ModbusWriteRegisterRequestBlueprint) writeRequest).getRegisters().getRegister(1).getValue(),
709 assertThat(((ModbusWriteRegisterRequestBlueprint) writeRequest).getRegisters().getRegister(2).getValue(),
713 ModbusWriteRequestBlueprint writeRequest = writeRequests.get(1);
714 assertThat(writeRequest.getFunctionCode(), is(equalTo(ModbusWriteFunctionCode.WRITE_SINGLE_REGISTER)));
715 assertThat(writeRequest.getReference(), is(equalTo(555)));
716 assertThat(((ModbusWriteRegisterRequestBlueprint) writeRequest).getRegisters().size(), is(equalTo(1)));
717 assertThat(((ModbusWriteRegisterRequestBlueprint) writeRequest).getRegisters().getRegister(0).getValue(),
722 private void testValueTypeGeneric(ModbusReadFunctionCode functionCode, ValueType valueType,
723 ThingStatus expectedStatus) {
724 ModbusSlaveEndpoint endpoint = new ModbusTCPSlaveEndpoint("thisishost", 502);
726 // Minimally mocked request
727 ModbusReadRequestBlueprint request = Mockito.mock(ModbusReadRequestBlueprint.class);
728 doReturn(3).when(request).getDataLength();
729 doReturn(functionCode).when(request).getFunctionCode();
731 PollTask task = Mockito.mock(PollTask.class);
732 doReturn(endpoint).when(task).getEndpoint();
733 doReturn(request).when(task).getRequest();
735 Bridge poller = createPollerMock("poller1", task);
737 Configuration dataConfig = new Configuration();
738 dataConfig.put("readStart", "1");
739 dataConfig.put("readTransform", "default");
740 dataConfig.put("readValueType", valueType.getConfigValue());
741 ModbusDataThingHandler dataHandler = createDataHandler("data1", poller,
742 builder -> builder.withConfiguration(dataConfig));
743 assertThat(dataHandler.getThing().getStatus(), is(equalTo(expectedStatus)));
747 public void testCoilDoesNotAcceptFloat32ValueType() {
748 testValueTypeGeneric(ModbusReadFunctionCode.READ_COILS, ModbusConstants.ValueType.FLOAT32, ThingStatus.OFFLINE);
752 public void testCoilAcceptsBitValueType() {
753 testValueTypeGeneric(ModbusReadFunctionCode.READ_COILS, ModbusConstants.ValueType.BIT, ThingStatus.ONLINE);
757 public void testDiscreteInputDoesNotAcceptFloat32ValueType() {
758 testValueTypeGeneric(ModbusReadFunctionCode.READ_INPUT_DISCRETES, ModbusConstants.ValueType.FLOAT32,
759 ThingStatus.OFFLINE);
763 public void testDiscreteInputAcceptsBitValueType() {
764 testValueTypeGeneric(ModbusReadFunctionCode.READ_INPUT_DISCRETES, ModbusConstants.ValueType.BIT,
769 public void testRefreshOnData() throws InterruptedException {
770 ModbusReadFunctionCode functionCode = ModbusReadFunctionCode.READ_COILS;
772 ModbusSlaveEndpoint endpoint = new ModbusTCPSlaveEndpoint("thisishost", 502);
776 // Minimally mocked request
777 ModbusReadRequestBlueprint request = Mockito.mock(ModbusReadRequestBlueprint.class);
778 doReturn(pollLength).when(request).getDataLength();
779 doReturn(functionCode).when(request).getFunctionCode();
781 PollTask task = Mockito.mock(PollTask.class);
782 doReturn(endpoint).when(task).getEndpoint();
783 doReturn(request).when(task).getRequest();
785 Bridge poller = createPollerMock("poller1", task);
787 Configuration dataConfig = new Configuration();
788 dataConfig.put("readStart", "0");
789 dataConfig.put("readTransform", "default");
790 dataConfig.put("readValueType", "bit");
792 String thingId = "read1";
794 ModbusDataThingHandler dataHandler = createDataHandler(thingId, poller,
795 builder -> builder.withConfiguration(dataConfig), bundleContext);
796 assertThat(dataHandler.getThing().getStatus(), is(equalTo(ThingStatus.ONLINE)));
798 verify(comms, never()).submitOneTimePoll(eq(request), notNull(), notNull());
799 // Reset initial REFRESH commands to data thing channels from the Core
800 reset(poller.getHandler());
801 dataHandler.handleCommand(Mockito.mock(ChannelUID.class), RefreshType.REFRESH);
803 // data handler asynchronously calls the poller.refresh() -- it might take some time
804 // We check that refresh is finally called
805 waitForAssert(() -> verify((ModbusPollerThingHandler) poller.getHandler()).refresh(), 2500, 50);
810 * @param pollerFunctionCode poller function code. Use null if you want to have data thing direct child of endpoint
812 * @param config thing config
813 * @param statusConsumer assertion method for data thingstatus
815 private void testInitGeneric(ModbusReadFunctionCode pollerFunctionCode, Configuration config,
816 Consumer<ThingStatusInfo> statusConsumer) {
820 if (pollerFunctionCode == null) {
821 parent = createTcpMock();
822 ThingHandler foo = parent.getHandler();
825 ModbusSlaveEndpoint endpoint = new ModbusTCPSlaveEndpoint("thisishost", 502);
827 // Minimally mocked request
828 ModbusReadRequestBlueprint request = Mockito.mock(ModbusReadRequestBlueprint.class);
829 doReturn(pollLength).when(request).getDataLength();
830 doReturn(pollerFunctionCode).when(request).getFunctionCode();
832 PollTask task = Mockito.mock(PollTask.class);
833 doReturn(endpoint).when(task).getEndpoint();
834 doReturn(request).when(task).getRequest();
836 parent = createPollerMock("poller1", task);
839 String thingId = "read1";
841 ModbusDataThingHandler dataHandler = createDataHandler(thingId, parent,
842 builder -> builder.withConfiguration(config), bundleContext);
844 statusConsumer.accept(dataHandler.getThing().getStatusInfo());
848 public void testReadOnlyData() {
849 Configuration dataConfig = new Configuration();
850 dataConfig.put("readStart", "0");
851 dataConfig.put("readValueType", "bit");
852 testInitGeneric(ModbusReadFunctionCode.READ_COILS, dataConfig,
853 status -> assertThat(status.getStatus(), is(equalTo(ThingStatus.ONLINE))));
857 * readValueType=bit should be assumed with coils, so it's ok to skip it
860 public void testReadOnlyDataMissingValueTypeWithCoils() {
861 Configuration dataConfig = new Configuration();
862 dataConfig.put("readStart", "0");
863 // missing value type
864 testInitGeneric(ModbusReadFunctionCode.READ_COILS, dataConfig,
865 status -> assertThat(status.getStatus(), is(equalTo(ThingStatus.ONLINE))));
869 public void testReadOnlyDataInvalidValueType() {
870 Configuration dataConfig = new Configuration();
871 dataConfig.put("readStart", "0");
872 dataConfig.put("readValueType", "foobar");
873 testInitGeneric(ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS, dataConfig, status -> {
874 assertThat(status.getStatus(), is(equalTo(ThingStatus.OFFLINE)));
875 assertThat(status.getStatusDetail(), is(equalTo(ThingStatusDetail.CONFIGURATION_ERROR)));
880 * We do not assume value type with registers, not ok to skip it
883 public void testReadOnlyDataMissingValueTypeWithRegisters() {
884 Configuration dataConfig = new Configuration();
885 dataConfig.put("readStart", "0");
886 testInitGeneric(ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS, dataConfig, status -> {
887 assertThat(status.getStatus(), is(equalTo(ThingStatus.OFFLINE)));
888 assertThat(status.getStatusDetail(), is(equalTo(ThingStatusDetail.CONFIGURATION_ERROR)));
893 public void testWriteOnlyData() {
894 Configuration dataConfig = new Configuration();
895 dataConfig.put("writeStart", "0");
896 dataConfig.put("writeValueType", "bit");
897 dataConfig.put("writeType", "coil");
898 testInitGeneric(ModbusReadFunctionCode.READ_COILS, dataConfig,
899 status -> assertThat(status.getStatus(), is(equalTo(ThingStatus.ONLINE))));
903 public void testWriteHoldingInt16Data() {
904 Configuration dataConfig = new Configuration();
905 dataConfig.put("writeStart", "0");
906 dataConfig.put("writeValueType", "int16");
907 dataConfig.put("writeType", "holding");
908 testInitGeneric(ModbusReadFunctionCode.READ_COILS, dataConfig,
909 status -> assertThat(status.getStatus(), is(equalTo(ThingStatus.ONLINE))));
913 public void testWriteHoldingInt8Data() {
914 Configuration dataConfig = new Configuration();
915 dataConfig.put("writeStart", "0");
916 dataConfig.put("writeValueType", "int8");
917 dataConfig.put("writeType", "holding");
918 testInitGeneric(null, dataConfig, status -> {
919 assertThat(status.getStatus(), is(equalTo(ThingStatus.OFFLINE)));
920 assertThat(status.getStatusDetail(), is(equalTo(ThingStatusDetail.CONFIGURATION_ERROR)));
925 public void testWriteHoldingBitData() {
926 Configuration dataConfig = new Configuration();
927 dataConfig.put("writeStart", "0");
928 dataConfig.put("writeValueType", "bit");
929 dataConfig.put("writeType", "holding");
930 testInitGeneric(null, dataConfig, status -> {
931 assertThat(status.getStatus(), is(equalTo(ThingStatus.OFFLINE)));
932 assertThat(status.getStatusDetail(), is(equalTo(ThingStatusDetail.CONFIGURATION_ERROR)));
937 public void testWriteOnlyDataChildOfEndpoint() {
938 Configuration dataConfig = new Configuration();
939 dataConfig.put("writeStart", "0");
940 dataConfig.put("writeValueType", "bit");
941 dataConfig.put("writeType", "coil");
942 testInitGeneric(null, dataConfig, status -> assertThat(status.getStatus(), is(equalTo(ThingStatus.ONLINE))));
946 public void testWriteOnlyDataMissingOneParameter() {
947 Configuration dataConfig = new Configuration();
948 dataConfig.put("writeStart", "0");
949 dataConfig.put("writeValueType", "bit");
950 // missing writeType --> error
951 testInitGeneric(ModbusReadFunctionCode.READ_COILS, dataConfig, status -> {
952 assertThat(status.getStatus(), is(equalTo(ThingStatus.OFFLINE)));
953 assertThat(status.getStatusDetail(), is(equalTo(ThingStatusDetail.CONFIGURATION_ERROR)));
954 assertThat(status.getDescription(), is(not(equalTo(null))));
959 * OK to omit writeValueType with coils since bit is assumed
962 public void testWriteOnlyDataMissingValueTypeWithCoilParameter() {
963 Configuration dataConfig = new Configuration();
964 dataConfig.put("writeStart", "0");
965 dataConfig.put("writeType", "coil");
966 testInitGeneric(ModbusReadFunctionCode.READ_COILS, dataConfig,
967 status -> assertThat(status.getStatus(), is(equalTo(ThingStatus.ONLINE))));
971 public void testWriteOnlyIllegalValueType() {
972 Configuration dataConfig = new Configuration();
973 dataConfig.put("writeStart", "0");
974 dataConfig.put("writeType", "coil");
975 dataConfig.put("writeValueType", "foobar");
976 testInitGeneric(ModbusReadFunctionCode.READ_COILS, dataConfig, status -> {
977 assertThat(status.getStatus(), is(equalTo(ThingStatus.OFFLINE)));
978 assertThat(status.getStatusDetail(), is(equalTo(ThingStatusDetail.CONFIGURATION_ERROR)));
983 public void testWriteInvalidType() {
984 Configuration dataConfig = new Configuration();
985 dataConfig.put("writeStart", "0");
986 dataConfig.put("writeType", "foobar");
987 testInitGeneric(ModbusReadFunctionCode.READ_COILS, dataConfig, status -> {
988 assertThat(status.getStatus(), is(equalTo(ThingStatus.OFFLINE)));
989 assertThat(status.getStatusDetail(), is(equalTo(ThingStatusDetail.CONFIGURATION_ERROR)));
994 public void testWriteCoilBadStart() {
995 Configuration dataConfig = new Configuration();
996 dataConfig.put("writeStart", "0.4");
997 dataConfig.put("writeType", "coil");
998 testInitGeneric(null, dataConfig, status -> {
999 assertThat(status.getStatus(), is(equalTo(ThingStatus.OFFLINE)));
1000 assertThat(status.getStatusDetail(), is(equalTo(ThingStatusDetail.CONFIGURATION_ERROR)));
1005 public void testWriteHoldingBadStart() {
1006 Configuration dataConfig = new Configuration();
1007 dataConfig.put("writeStart", "0.4");
1008 dataConfig.put("writeType", "holding");
1009 testInitGeneric(null, dataConfig, status -> {
1010 assertThat(status.getStatus(), is(equalTo(ThingStatus.OFFLINE)));
1011 assertThat(status.getStatusDetail(), is(equalTo(ThingStatusDetail.CONFIGURATION_ERROR)));
1016 public void testReadHoldingBadStart() {
1017 Configuration dataConfig = new Configuration();
1018 dataConfig.put("readStart", "0.0");
1019 dataConfig.put("readValueType", "int16");
1020 testInitGeneric(ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS, dataConfig, status -> {
1021 assertThat(status.getStatus(), is(equalTo(ThingStatus.OFFLINE)));
1022 assertThat(status.getStatusDetail(), is(equalTo(ThingStatusDetail.CONFIGURATION_ERROR)));
1027 public void testReadHoldingBadStart2() {
1028 Configuration dataConfig = new Configuration();
1029 dataConfig.put("readStart", "0.0");
1030 dataConfig.put("readValueType", "bit");
1031 testInitGeneric(ModbusReadFunctionCode.READ_COILS, dataConfig, status -> {
1032 assertThat(status.getStatus(), is(equalTo(ThingStatus.OFFLINE)));
1033 assertThat(status.getStatusDetail(), is(equalTo(ThingStatusDetail.CONFIGURATION_ERROR)));
1038 public void testReadHoldingOKStart() {
1039 Configuration dataConfig = new Configuration();
1040 dataConfig.put("readStart", "0.0");
1041 dataConfig.put("readType", "holding");
1042 dataConfig.put("readValueType", "bit");
1043 testInitGeneric(ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS, dataConfig,
1044 status -> assertThat(status.getStatus(), is(equalTo(ThingStatus.ONLINE))));
1048 public void testReadValueTypeIllegal() {
1049 Configuration dataConfig = new Configuration();
1050 dataConfig.put("readStart", "0.0");
1051 dataConfig.put("readType", "holding");
1052 dataConfig.put("readValueType", "foobar");
1053 testInitGeneric(ModbusReadFunctionCode.READ_COILS, dataConfig, status -> {
1054 assertThat(status.getStatus(), is(equalTo(ThingStatus.OFFLINE)));
1055 assertThat(status.getStatusDetail(), is(equalTo(ThingStatusDetail.CONFIGURATION_ERROR)));
1060 public void testWriteOnlyTransform() {
1061 Configuration dataConfig = new Configuration();
1062 // no need to have start, JSON output of transformation defines everything
1063 dataConfig.put("writeTransform", "JS(myJsonTransform.js)");
1064 testInitGeneric(null, dataConfig, status -> assertThat(status.getStatus(), is(equalTo(ThingStatus.ONLINE))));
1068 public void testWriteTransformAndStart() {
1069 Configuration dataConfig = new Configuration();
1070 // It's illegal to have start and transform. Just have transform or have all
1071 dataConfig.put("writeStart", "3");
1072 dataConfig.put("writeTransform", "JS(myJsonTransform.js)");
1073 testInitGeneric(ModbusReadFunctionCode.READ_COILS, dataConfig, status -> {
1074 assertThat(status.getStatus(), is(equalTo(ThingStatus.OFFLINE)));
1075 assertThat(status.getStatusDetail(), is(equalTo(ThingStatusDetail.CONFIGURATION_ERROR)));
1080 public void testWriteTransformAndNecessary() {
1081 Configuration dataConfig = new Configuration();
1082 // It's illegal to have start and transform. Just have transform or have all
1083 dataConfig.put("writeStart", "3");
1084 dataConfig.put("writeType", "holding");
1085 dataConfig.put("writeValueType", "int16");
1086 dataConfig.put("writeTransform", "JS(myJsonTransform.js)");
1087 testInitGeneric(null, dataConfig, status -> assertThat(status.getStatus(), is(equalTo(ThingStatus.ONLINE))));