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.apache.commons.lang.StringUtils;
35 import org.hamcrest.Matcher;
36 import org.junit.jupiter.api.AfterEach;
37 import org.junit.jupiter.api.Disabled;
38 import org.junit.jupiter.api.Test;
39 import org.mockito.Mockito;
40 import org.openhab.binding.modbus.handler.EndpointNotInitializedException;
41 import org.openhab.binding.modbus.handler.ModbusPollerThingHandler;
42 import org.openhab.binding.modbus.internal.handler.ModbusDataThingHandler;
43 import org.openhab.binding.modbus.internal.handler.ModbusTcpThingHandler;
44 import org.openhab.core.config.core.Configuration;
45 import org.openhab.core.io.transport.modbus.AsyncModbusFailure;
46 import org.openhab.core.io.transport.modbus.AsyncModbusReadResult;
47 import org.openhab.core.io.transport.modbus.AsyncModbusWriteResult;
48 import org.openhab.core.io.transport.modbus.BitArray;
49 import org.openhab.core.io.transport.modbus.ModbusConstants;
50 import org.openhab.core.io.transport.modbus.ModbusConstants.ValueType;
51 import org.openhab.core.io.transport.modbus.ModbusReadFunctionCode;
52 import org.openhab.core.io.transport.modbus.ModbusReadRequestBlueprint;
53 import org.openhab.core.io.transport.modbus.ModbusRegisterArray;
54 import org.openhab.core.io.transport.modbus.ModbusResponse;
55 import org.openhab.core.io.transport.modbus.ModbusWriteCoilRequestBlueprint;
56 import org.openhab.core.io.transport.modbus.ModbusWriteFunctionCode;
57 import org.openhab.core.io.transport.modbus.ModbusWriteRegisterRequestBlueprint;
58 import org.openhab.core.io.transport.modbus.ModbusWriteRequestBlueprint;
59 import org.openhab.core.io.transport.modbus.PollTask;
60 import org.openhab.core.io.transport.modbus.endpoint.ModbusSlaveEndpoint;
61 import org.openhab.core.io.transport.modbus.endpoint.ModbusTCPSlaveEndpoint;
62 import org.openhab.core.items.GenericItem;
63 import org.openhab.core.items.Item;
64 import org.openhab.core.library.items.DateTimeItem;
65 import org.openhab.core.library.types.DecimalType;
66 import org.openhab.core.library.types.OnOffType;
67 import org.openhab.core.library.types.OpenClosedType;
68 import org.openhab.core.library.types.StringType;
69 import org.openhab.core.thing.Bridge;
70 import org.openhab.core.thing.ChannelUID;
71 import org.openhab.core.thing.Thing;
72 import org.openhab.core.thing.ThingStatus;
73 import org.openhab.core.thing.ThingStatusDetail;
74 import org.openhab.core.thing.ThingStatusInfo;
75 import org.openhab.core.thing.ThingUID;
76 import org.openhab.core.thing.binding.ThingHandler;
77 import org.openhab.core.thing.binding.builder.BridgeBuilder;
78 import org.openhab.core.thing.binding.builder.ChannelBuilder;
79 import org.openhab.core.thing.binding.builder.ThingBuilder;
80 import org.openhab.core.transform.TransformationException;
81 import org.openhab.core.transform.TransformationService;
82 import org.openhab.core.types.Command;
83 import org.openhab.core.types.RefreshType;
84 import org.openhab.core.types.State;
85 import org.openhab.core.types.UnDefType;
86 import org.osgi.framework.BundleContext;
87 import org.osgi.framework.InvalidSyntaxException;
90 * @author Sami Salonen - Initial contribution
92 public class ModbusDataHandlerTest extends AbstractModbusOSGiTest {
94 private final class MultiplyTransformation implements TransformationService {
96 public String transform(String function, String source) throws TransformationException {
97 return String.valueOf(Integer.parseInt(function) * Integer.parseInt(source));
101 private static final Map<String, String> CHANNEL_TO_ACCEPTED_TYPE = new HashMap<>();
103 CHANNEL_TO_ACCEPTED_TYPE.put(CHANNEL_SWITCH, "Switch");
104 CHANNEL_TO_ACCEPTED_TYPE.put(CHANNEL_CONTACT, "Contact");
105 CHANNEL_TO_ACCEPTED_TYPE.put(CHANNEL_DATETIME, "DateTime");
106 CHANNEL_TO_ACCEPTED_TYPE.put(CHANNEL_DIMMER, "Dimmer");
107 CHANNEL_TO_ACCEPTED_TYPE.put(CHANNEL_NUMBER, "Number");
108 CHANNEL_TO_ACCEPTED_TYPE.put(CHANNEL_STRING, "String");
109 CHANNEL_TO_ACCEPTED_TYPE.put(CHANNEL_ROLLERSHUTTER, "Rollershutter");
110 CHANNEL_TO_ACCEPTED_TYPE.put(CHANNEL_LAST_READ_SUCCESS, "DateTime");
111 CHANNEL_TO_ACCEPTED_TYPE.put(CHANNEL_LAST_WRITE_SUCCESS, "DateTime");
112 CHANNEL_TO_ACCEPTED_TYPE.put(CHANNEL_LAST_WRITE_ERROR, "DateTime");
113 CHANNEL_TO_ACCEPTED_TYPE.put(CHANNEL_LAST_READ_ERROR, "DateTime");
115 private List<ModbusWriteRequestBlueprint> writeRequests = new ArrayList<>();
118 public void tearDown() {
119 writeRequests.clear();
122 private void captureModbusWrites() {
123 Mockito.when(comms.submitOneTimeWrite(any(), any(), any())).then(invocation -> {
124 ModbusWriteRequestBlueprint task = (ModbusWriteRequestBlueprint) invocation.getArgument(0);
125 writeRequests.add(task);
126 return Mockito.mock(ScheduledFuture.class);
130 private Bridge createPollerMock(String pollerId, PollTask task) {
132 ThingUID thingUID = new ThingUID(THING_TYPE_MODBUS_POLLER, pollerId);
133 BridgeBuilder builder = BridgeBuilder.create(THING_TYPE_MODBUS_POLLER, thingUID)
134 .withLabel("label for " + pollerId);
135 for (Entry<String, String> entry : CHANNEL_TO_ACCEPTED_TYPE.entrySet()) {
136 String channelId = entry.getKey();
137 String channelAcceptedType = entry.getValue();
138 builder = builder.withChannel(
139 ChannelBuilder.create(new ChannelUID(thingUID, channelId), channelAcceptedType).build());
141 poller = builder.build();
142 poller.setStatusInfo(new ThingStatusInfo(ThingStatus.ONLINE, ThingStatusDetail.NONE, ""));
144 ModbusPollerThingHandler mockHandler = Mockito.mock(ModbusPollerThingHandler.class);
145 doReturn(task.getRequest()).when(mockHandler).getRequest();
146 assert comms != null;
147 doReturn(comms).when(mockHandler).getCommunicationInterface();
148 doReturn(task.getEndpoint()).when(comms).getEndpoint();
149 poller.setHandler(mockHandler);
150 assertSame(poller.getHandler(), mockHandler);
151 assertSame(((ModbusPollerThingHandler) poller.getHandler()).getCommunicationInterface().getEndpoint(),
153 assertSame(((ModbusPollerThingHandler) poller.getHandler()).getRequest(), task.getRequest());
159 private Bridge createTcpMock() {
160 ModbusSlaveEndpoint endpoint = new ModbusTCPSlaveEndpoint("thisishost", 502, false);
161 Bridge tcpBridge = ModbusPollerThingHandlerTest.createTcpThingBuilder("tcp1").build();
162 ModbusTcpThingHandler tcpThingHandler = Mockito.mock(ModbusTcpThingHandler.class);
163 tcpBridge.setStatusInfo(new ThingStatusInfo(ThingStatus.ONLINE, ThingStatusDetail.NONE, ""));
164 tcpBridge.setHandler(tcpThingHandler);
165 doReturn(comms).when(tcpThingHandler).getCommunicationInterface();
167 doReturn(0).when(tcpThingHandler).getSlaveId();
168 } catch (EndpointNotInitializedException e) {
169 // not raised -- we are mocking return value only, not actually calling the method
170 throw new IllegalStateException();
172 tcpThingHandler.initialize();
173 assertThat(tcpBridge.getStatus(), is(equalTo(ThingStatus.ONLINE)));
177 private ModbusDataThingHandler createDataHandler(String id, Bridge bridge,
178 Function<ThingBuilder, ThingBuilder> builderConfigurator) {
179 return createDataHandler(id, bridge, builderConfigurator, null);
182 private ModbusDataThingHandler createDataHandler(String id, Bridge bridge,
183 Function<ThingBuilder, ThingBuilder> builderConfigurator, BundleContext context) {
184 return createDataHandler(id, bridge, builderConfigurator, context, true);
187 private ModbusDataThingHandler createDataHandler(String id, Bridge bridge,
188 Function<ThingBuilder, ThingBuilder> builderConfigurator, BundleContext context,
189 boolean autoCreateItemsAndLinkToChannels) {
190 ThingUID thingUID = new ThingUID(THING_TYPE_MODBUS_DATA, id);
191 ThingBuilder builder = ThingBuilder.create(THING_TYPE_MODBUS_DATA, thingUID).withLabel("label for " + id);
192 Map<String, ChannelUID> toBeLinked = new HashMap<>();
193 for (Entry<String, String> entry : CHANNEL_TO_ACCEPTED_TYPE.entrySet()) {
194 String channelId = entry.getKey();
195 String channelAcceptedType = entry.getValue();
196 ChannelUID channelUID = new ChannelUID(thingUID, channelId);
197 builder = builder.withChannel(ChannelBuilder.create(channelUID, channelAcceptedType).build());
199 if (autoCreateItemsAndLinkToChannels) {
200 // Create item of correct type and link it to channel
201 String itemName = getItemName(channelUID);
202 final GenericItem item;
203 if (channelId.startsWith("last") || channelId.equals("datetime")) {
204 item = new DateTimeItem(itemName);
206 item = coreItemFactory.createItem(StringUtils.capitalize(channelId), itemName);
208 assertThat(String.format("Could not determine correct item type for %s", channelId), item,
211 Objects.requireNonNull(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, false);
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, false);
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, false);
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 byte[] { (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 byte[] { (byte) 0xff, (byte) 0xfd }), null, bundleContext);
548 assertSingleStateUpdate(dataHandler, CHANNEL_LAST_READ_SUCCESS, is(notNullValue(State.class)));
549 assertSingleStateUpdate(dataHandler, CHANNEL_LAST_READ_ERROR, is(nullValue(State.class)));
551 // transformation output (-30) is not valid for contact or switch
552 assertSingleStateUpdate(dataHandler, CHANNEL_CONTACT, is(nullValue(State.class)));
553 assertSingleStateUpdate(dataHandler, CHANNEL_SWITCH, is(nullValue(State.class)));
554 // -30 is not valid value for Dimmer (PercentType) (not between 0...100)
555 assertSingleStateUpdate(dataHandler, CHANNEL_DIMMER, is(nullValue(State.class)));
556 assertSingleStateUpdate(dataHandler, CHANNEL_NUMBER, new DecimalType(-30));
557 // roller shutter fails since -3 is invalid value (not between 0...100)
558 assertSingleStateUpdate(dataHandler, CHANNEL_ROLLERSHUTTER, is(nullValue(State.class)));
559 assertSingleStateUpdate(dataHandler, CHANNEL_STRING, new StringType("-30"));
560 // no datetime, conversion not possible without transformation
564 public void testOnRegistersNaNFloatInRegisters() throws InvalidSyntaxException {
565 ModbusDataThingHandler dataHandler = testReadHandlingGeneric(ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS,
566 "0", "default", ModbusConstants.ValueType.FLOAT32, null, new ModbusRegisterArray(
567 // equivalent of floating point NaN
568 new byte[] { (byte) 0x7f, (byte) 0xc0, (byte) 0x00, (byte) 0x00 }),
569 null, bundleContext);
571 assertSingleStateUpdate(dataHandler, CHANNEL_LAST_READ_SUCCESS, is(notNullValue(State.class)));
572 assertSingleStateUpdate(dataHandler, CHANNEL_LAST_READ_ERROR, is(nullValue(State.class)));
574 // UNDEF is treated as "boolean true" (OPEN/ON) since it is != 0.
575 assertSingleStateUpdate(dataHandler, CHANNEL_CONTACT, OpenClosedType.OPEN);
576 assertSingleStateUpdate(dataHandler, CHANNEL_SWITCH, OnOffType.ON);
577 assertSingleStateUpdate(dataHandler, CHANNEL_DIMMER, OnOffType.ON);
578 assertSingleStateUpdate(dataHandler, CHANNEL_NUMBER, UnDefType.UNDEF);
579 assertSingleStateUpdate(dataHandler, CHANNEL_ROLLERSHUTTER, UnDefType.UNDEF);
580 assertSingleStateUpdate(dataHandler, CHANNEL_STRING, UnDefType.UNDEF);
584 public void testOnRegistersRealTransformation2() throws InvalidSyntaxException {
585 mockTransformation("ONOFF", new TransformationService() {
588 public String transform(String function, String source) throws TransformationException {
589 return Integer.parseInt(source) != 0 ? "ON" : "OFF";
592 ModbusDataThingHandler dataHandler = testReadHandlingGeneric(ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS,
593 "0", "ONOFF(10)", ModbusConstants.ValueType.INT16, null,
594 new ModbusRegisterArray(new byte[] { (byte) 0xff, (byte) 0xfd }), null, bundleContext);
596 assertSingleStateUpdate(dataHandler, CHANNEL_LAST_READ_SUCCESS, is(notNullValue(State.class)));
597 assertSingleStateUpdate(dataHandler, CHANNEL_LAST_READ_ERROR, is(nullValue(State.class)));
599 assertSingleStateUpdate(dataHandler, CHANNEL_CONTACT, is(nullValue(State.class)));
600 assertSingleStateUpdate(dataHandler, CHANNEL_SWITCH, is(equalTo(OnOffType.ON)));
601 assertSingleStateUpdate(dataHandler, CHANNEL_DIMMER, is(equalTo(OnOffType.ON)));
602 assertSingleStateUpdate(dataHandler, CHANNEL_NUMBER, is(nullValue(State.class)));
603 assertSingleStateUpdate(dataHandler, CHANNEL_ROLLERSHUTTER, is(nullValue(State.class)));
604 assertSingleStateUpdate(dataHandler, CHANNEL_STRING, is(equalTo(new StringType("ON"))));
608 public void testWriteRealTransformation() throws InvalidSyntaxException {
609 captureModbusWrites();
610 mockTransformation("MULTIPLY", new MultiplyTransformation());
611 ModbusDataThingHandler dataHandler = testWriteHandlingGeneric("50", "MULTIPLY(10)",
612 ModbusConstants.ValueType.BIT, "coil", ModbusWriteFunctionCode.WRITE_COIL, "number",
613 new DecimalType("2"), null, bundleContext);
615 assertSingleStateUpdate(dataHandler, CHANNEL_LAST_WRITE_SUCCESS, is(notNullValue(State.class)));
616 assertSingleStateUpdate(dataHandler, CHANNEL_LAST_WRITE_ERROR, is(nullValue(State.class)));
617 assertThat(writeRequests.size(), is(equalTo(1)));
618 ModbusWriteRequestBlueprint writeRequest = writeRequests.get(0);
619 assertThat(writeRequest.getFunctionCode(), is(equalTo(ModbusWriteFunctionCode.WRITE_COIL)));
620 assertThat(writeRequest.getReference(), is(equalTo(50)));
621 assertThat(((ModbusWriteCoilRequestBlueprint) writeRequest).getCoils().size(), is(equalTo(1)));
622 // Since transform output is non-zero, it is mapped as "true"
623 assertThat(((ModbusWriteCoilRequestBlueprint) writeRequest).getCoils().getBit(0), is(equalTo(true)));
627 public void testWriteRealTransformation2() throws InvalidSyntaxException {
628 captureModbusWrites();
629 mockTransformation("ZERO", new TransformationService() {
632 public String transform(String function, String source) throws TransformationException {
636 ModbusDataThingHandler dataHandler = testWriteHandlingGeneric("50", "ZERO(foobar)",
637 ModbusConstants.ValueType.BIT, "coil", ModbusWriteFunctionCode.WRITE_COIL, "number",
638 new DecimalType("2"), null, bundleContext);
640 assertSingleStateUpdate(dataHandler, CHANNEL_LAST_WRITE_SUCCESS, is(notNullValue(State.class)));
641 assertSingleStateUpdate(dataHandler, CHANNEL_LAST_WRITE_ERROR, is(nullValue(State.class)));
642 assertThat(writeRequests.size(), is(equalTo(1)));
643 ModbusWriteRequestBlueprint writeRequest = writeRequests.get(0);
644 assertThat(writeRequest.getFunctionCode(), is(equalTo(ModbusWriteFunctionCode.WRITE_COIL)));
645 assertThat(writeRequest.getReference(), is(equalTo(50)));
646 assertThat(((ModbusWriteCoilRequestBlueprint) writeRequest).getCoils().size(), is(equalTo(1)));
647 // Since transform output is zero, it is mapped as "false"
648 assertThat(((ModbusWriteCoilRequestBlueprint) writeRequest).getCoils().getBit(0), is(equalTo(false)));
652 public void testWriteRealTransformation3() throws InvalidSyntaxException {
653 captureModbusWrites();
654 mockTransformation("RANDOM", new TransformationService() {
657 public String transform(String function, String source) throws TransformationException {
661 ModbusDataThingHandler dataHandler = testWriteHandlingGeneric("50", "RANDOM(foobar)",
662 ModbusConstants.ValueType.INT16, "holding", ModbusWriteFunctionCode.WRITE_SINGLE_REGISTER, "number",
663 new DecimalType("2"), null, bundleContext);
665 assertSingleStateUpdate(dataHandler, CHANNEL_LAST_WRITE_SUCCESS, is(notNullValue(State.class)));
666 assertSingleStateUpdate(dataHandler, CHANNEL_LAST_WRITE_ERROR, is(nullValue(State.class)));
667 assertThat(writeRequests.size(), is(equalTo(1)));
668 ModbusWriteRequestBlueprint writeRequest = writeRequests.get(0);
669 assertThat(writeRequest.getFunctionCode(), is(equalTo(ModbusWriteFunctionCode.WRITE_SINGLE_REGISTER)));
670 assertThat(writeRequest.getReference(), is(equalTo(50)));
671 assertThat(((ModbusWriteRegisterRequestBlueprint) writeRequest).getRegisters().size(), is(equalTo(1)));
672 assertThat(((ModbusWriteRegisterRequestBlueprint) writeRequest).getRegisters().getRegister(0), is(equalTo(5)));
676 public void testWriteRealTransformation4() throws InvalidSyntaxException {
677 captureModbusWrites();
678 mockTransformation("JSON", new TransformationService() {
681 public String transform(String function, String source) throws TransformationException {
683 + "\"functionCode\": 16,"//
684 + "\"address\": 5412,"//
685 + "\"value\": [1, 0, 5]"//
688 + "\"functionCode\": 6,"//
689 + "\"address\": 555,"//
694 ModbusDataThingHandler dataHandler = testWriteHandlingGeneric("50", "JSON(foobar)",
695 ModbusConstants.ValueType.INT16, "holding", ModbusWriteFunctionCode.WRITE_MULTIPLE_REGISTERS, "number",
696 new DecimalType("2"), null, bundleContext);
698 assertSingleStateUpdate(dataHandler, CHANNEL_LAST_WRITE_SUCCESS, is(notNullValue(State.class)));
699 assertSingleStateUpdate(dataHandler, CHANNEL_LAST_WRITE_ERROR, is(nullValue(State.class)));
700 assertThat(writeRequests.size(), is(equalTo(2)));
702 ModbusWriteRequestBlueprint writeRequest = writeRequests.get(0);
703 assertThat(writeRequest.getFunctionCode(), is(equalTo(ModbusWriteFunctionCode.WRITE_MULTIPLE_REGISTERS)));
704 assertThat(writeRequest.getReference(), is(equalTo(5412)));
705 assertThat(((ModbusWriteRegisterRequestBlueprint) writeRequest).getRegisters().size(), is(equalTo(3)));
706 assertThat(((ModbusWriteRegisterRequestBlueprint) writeRequest).getRegisters().getRegister(0),
708 assertThat(((ModbusWriteRegisterRequestBlueprint) writeRequest).getRegisters().getRegister(1),
710 assertThat(((ModbusWriteRegisterRequestBlueprint) writeRequest).getRegisters().getRegister(2),
714 ModbusWriteRequestBlueprint writeRequest = writeRequests.get(1);
715 assertThat(writeRequest.getFunctionCode(), is(equalTo(ModbusWriteFunctionCode.WRITE_SINGLE_REGISTER)));
716 assertThat(writeRequest.getReference(), is(equalTo(555)));
717 assertThat(((ModbusWriteRegisterRequestBlueprint) writeRequest).getRegisters().size(), is(equalTo(1)));
718 assertThat(((ModbusWriteRegisterRequestBlueprint) writeRequest).getRegisters().getRegister(0),
723 private void testValueTypeGeneric(ModbusReadFunctionCode functionCode, ValueType valueType,
724 ThingStatus expectedStatus) {
725 ModbusSlaveEndpoint endpoint = new ModbusTCPSlaveEndpoint("thisishost", 502, false);
727 // Minimally mocked request
728 ModbusReadRequestBlueprint request = Mockito.mock(ModbusReadRequestBlueprint.class);
729 doReturn(3).when(request).getDataLength();
730 doReturn(functionCode).when(request).getFunctionCode();
732 PollTask task = Mockito.mock(PollTask.class);
733 doReturn(endpoint).when(task).getEndpoint();
734 doReturn(request).when(task).getRequest();
736 Bridge poller = createPollerMock("poller1", task);
738 Configuration dataConfig = new Configuration();
739 dataConfig.put("readStart", "1");
740 dataConfig.put("readTransform", "default");
741 dataConfig.put("readValueType", valueType.getConfigValue());
742 ModbusDataThingHandler dataHandler = createDataHandler("data1", poller,
743 builder -> builder.withConfiguration(dataConfig));
744 assertThat(dataHandler.getThing().getStatus(), is(equalTo(expectedStatus)));
748 public void testCoilDoesNotAcceptFloat32ValueType() {
749 testValueTypeGeneric(ModbusReadFunctionCode.READ_COILS, ModbusConstants.ValueType.FLOAT32, ThingStatus.OFFLINE);
753 public void testCoilAcceptsBitValueType() {
754 testValueTypeGeneric(ModbusReadFunctionCode.READ_COILS, ModbusConstants.ValueType.BIT, ThingStatus.ONLINE);
758 public void testDiscreteInputDoesNotAcceptFloat32ValueType() {
759 testValueTypeGeneric(ModbusReadFunctionCode.READ_INPUT_DISCRETES, ModbusConstants.ValueType.FLOAT32,
760 ThingStatus.OFFLINE);
764 public void testDiscreteInputAcceptsBitValueType() {
765 testValueTypeGeneric(ModbusReadFunctionCode.READ_INPUT_DISCRETES, ModbusConstants.ValueType.BIT,
770 @Disabled("See: https://github.com/openhab/openhab-addons/issues/9617")
771 public void testRefreshOnData() throws InterruptedException {
772 ModbusReadFunctionCode functionCode = ModbusReadFunctionCode.READ_COILS;
774 ModbusSlaveEndpoint endpoint = new ModbusTCPSlaveEndpoint("thisishost", 502, false);
778 // Minimally mocked request
779 ModbusReadRequestBlueprint request = Mockito.mock(ModbusReadRequestBlueprint.class);
780 doReturn(pollLength).when(request).getDataLength();
781 doReturn(functionCode).when(request).getFunctionCode();
783 PollTask task = Mockito.mock(PollTask.class);
784 doReturn(endpoint).when(task).getEndpoint();
785 doReturn(request).when(task).getRequest();
787 Bridge poller = createPollerMock("poller1", task);
789 Configuration dataConfig = new Configuration();
790 dataConfig.put("readStart", "0");
791 dataConfig.put("readTransform", "default");
792 dataConfig.put("readValueType", "bit");
794 String thingId = "read1";
796 ModbusDataThingHandler dataHandler = createDataHandler(thingId, poller,
797 builder -> builder.withConfiguration(dataConfig), bundleContext);
798 assertThat(dataHandler.getThing().getStatus(), is(equalTo(ThingStatus.ONLINE)));
800 verify(comms, never()).submitOneTimePoll(eq(request), notNull(), notNull());
801 // Reset initial REFRESH commands to data thing channels from the Core
802 reset(poller.getHandler());
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(), 2500, 50);
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))));