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