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.builder.BridgeBuilder;
74 import org.openhab.core.thing.binding.builder.ChannelBuilder;
75 import org.openhab.core.thing.binding.builder.ThingBuilder;
76 import org.openhab.core.transform.TransformationException;
77 import org.openhab.core.transform.TransformationService;
78 import org.openhab.core.types.Command;
79 import org.openhab.core.types.RefreshType;
80 import org.openhab.core.types.State;
81 import org.openhab.core.types.UnDefType;
82 import org.osgi.framework.BundleContext;
83 import org.osgi.framework.InvalidSyntaxException;
86 * @author Sami Salonen - Initial contribution
88 public class ModbusDataHandlerTest extends AbstractModbusOSGiTest {
90 private final class MultiplyTransformation implements TransformationService {
92 public String transform(String function, String source) throws TransformationException {
93 return String.valueOf(Integer.parseInt(function) * Integer.parseInt(source));
97 private static final Map<String, String> CHANNEL_TO_ACCEPTED_TYPE = new HashMap<>();
99 CHANNEL_TO_ACCEPTED_TYPE.put(CHANNEL_SWITCH, "Switch");
100 CHANNEL_TO_ACCEPTED_TYPE.put(CHANNEL_CONTACT, "Contact");
101 CHANNEL_TO_ACCEPTED_TYPE.put(CHANNEL_DATETIME, "DateTime");
102 CHANNEL_TO_ACCEPTED_TYPE.put(CHANNEL_DIMMER, "Dimmer");
103 CHANNEL_TO_ACCEPTED_TYPE.put(CHANNEL_NUMBER, "Number");
104 CHANNEL_TO_ACCEPTED_TYPE.put(CHANNEL_STRING, "String");
105 CHANNEL_TO_ACCEPTED_TYPE.put(CHANNEL_ROLLERSHUTTER, "Rollershutter");
106 CHANNEL_TO_ACCEPTED_TYPE.put(CHANNEL_LAST_READ_SUCCESS, "DateTime");
107 CHANNEL_TO_ACCEPTED_TYPE.put(CHANNEL_LAST_WRITE_SUCCESS, "DateTime");
108 CHANNEL_TO_ACCEPTED_TYPE.put(CHANNEL_LAST_WRITE_ERROR, "DateTime");
109 CHANNEL_TO_ACCEPTED_TYPE.put(CHANNEL_LAST_READ_ERROR, "DateTime");
111 private List<ModbusWriteRequestBlueprint> writeRequests = new ArrayList<>();
114 public void tearDown() {
115 writeRequests.clear();
118 private void captureModbusWrites() {
119 Mockito.when(comms.submitOneTimeWrite(any(), any(), any())).then(invocation -> {
120 ModbusWriteRequestBlueprint task = (ModbusWriteRequestBlueprint) invocation.getArgument(0);
121 writeRequests.add(task);
122 return Mockito.mock(ScheduledFuture.class);
126 private Bridge createPollerMock(String pollerId, PollTask task) {
128 ThingUID thingUID = new ThingUID(THING_TYPE_MODBUS_POLLER, pollerId);
129 BridgeBuilder builder = BridgeBuilder.create(THING_TYPE_MODBUS_POLLER, thingUID)
130 .withLabel("label for " + pollerId);
131 for (Entry<String, String> entry : CHANNEL_TO_ACCEPTED_TYPE.entrySet()) {
132 String channelId = entry.getKey();
133 String channelAcceptedType = entry.getValue();
134 builder = builder.withChannel(
135 ChannelBuilder.create(new ChannelUID(thingUID, channelId), channelAcceptedType).build());
137 poller = builder.build();
138 poller.setStatusInfo(new ThingStatusInfo(ThingStatus.ONLINE, ThingStatusDetail.NONE, ""));
140 ModbusPollerThingHandler mockHandler = Mockito.mock(ModbusPollerThingHandler.class);
141 doReturn(task.getRequest()).when(mockHandler).getRequest();
142 assert comms != null;
143 doReturn(comms).when(mockHandler).getCommunicationInterface();
144 doReturn(task.getEndpoint()).when(comms).getEndpoint();
145 poller.setHandler(mockHandler);
146 assertSame(poller.getHandler(), mockHandler);
147 assertSame(((ModbusPollerThingHandler) poller.getHandler()).getCommunicationInterface().getEndpoint(),
149 assertSame(((ModbusPollerThingHandler) poller.getHandler()).getRequest(), task.getRequest());
155 private Bridge createTcpMock() {
156 Bridge tcpBridge = ModbusPollerThingHandlerTest.createTcpThingBuilder("tcp1").build();
157 ModbusTcpThingHandler tcpThingHandler = Mockito.mock(ModbusTcpThingHandler.class);
158 tcpBridge.setStatusInfo(new ThingStatusInfo(ThingStatus.ONLINE, ThingStatusDetail.NONE, ""));
159 tcpBridge.setHandler(tcpThingHandler);
160 doReturn(comms).when(tcpThingHandler).getCommunicationInterface();
162 doReturn(0).when(tcpThingHandler).getSlaveId();
163 } catch (EndpointNotInitializedException e) {
164 // not raised -- we are mocking return value only, not actually calling the method
165 throw new IllegalStateException();
167 tcpThingHandler.initialize();
168 assertThat(tcpBridge.getStatus(), is(equalTo(ThingStatus.ONLINE)));
172 private ModbusDataThingHandler createDataHandler(String id, Bridge bridge,
173 Function<ThingBuilder, ThingBuilder> builderConfigurator) {
174 return createDataHandler(id, bridge, builderConfigurator, null);
177 private ModbusDataThingHandler createDataHandler(String id, Bridge bridge,
178 Function<ThingBuilder, ThingBuilder> builderConfigurator, BundleContext context) {
179 return createDataHandler(id, bridge, builderConfigurator, context, true);
182 private ModbusDataThingHandler createDataHandler(String id, Bridge bridge,
183 Function<ThingBuilder, ThingBuilder> builderConfigurator, BundleContext context,
184 boolean autoCreateItemsAndLinkToChannels) {
185 ThingUID thingUID = new ThingUID(THING_TYPE_MODBUS_DATA, id);
186 ThingBuilder builder = ThingBuilder.create(THING_TYPE_MODBUS_DATA, thingUID).withLabel("label for " + id);
187 Map<String, ChannelUID> toBeLinked = new HashMap<>();
188 for (Entry<String, String> entry : CHANNEL_TO_ACCEPTED_TYPE.entrySet()) {
189 String channelId = entry.getKey();
190 // accepted item type
191 String channelAcceptedType = entry.getValue();
192 ChannelUID channelUID = new ChannelUID(thingUID, channelId);
193 builder = builder.withChannel(ChannelBuilder.create(channelUID, channelAcceptedType).build());
195 if (autoCreateItemsAndLinkToChannels) {
196 // Create item of correct type and link it to channel
197 String itemName = getItemName(channelUID);
198 final GenericItem item;
199 item = coreItemFactory.createItem(channelAcceptedType, itemName);
200 assertThat(String.format("Could not determine correct item type for %s", channelId), item,
203 Objects.requireNonNull(item);
205 toBeLinked.put(itemName, channelUID);
208 if (builderConfigurator != null) {
209 builder = builderConfigurator.apply(builder);
212 Thing dataThing = builder.withBridge(bridge.getUID()).build();
215 // Link after the things and items have been created
216 for (Entry<String, ChannelUID> entry : toBeLinked.entrySet()) {
217 linkItem(entry.getKey(), entry.getValue());
219 return (ModbusDataThingHandler) dataThing.getHandler();
222 private String getItemName(ChannelUID channelUID) {
223 return channelUID.toString().replace(':', '_') + "_item";
226 private void assertSingleStateUpdate(ModbusDataThingHandler handler, String channel, Matcher<State> matcher) {
227 waitForAssert(() -> {
228 ChannelUID channelUID = new ChannelUID(handler.getThing().getUID(), channel);
229 String itemName = getItemName(channelUID);
230 Item item = itemRegistry.get(itemName);
231 assertThat(String.format("Item %s is not available from item registry", itemName), item,
234 List<State> updates = getStateUpdates(itemName);
235 if (updates != null) {
237 String.format("Many updates found, expected one: %s", Arrays.deepToString(updates.toArray())),
238 updates.size(), is(equalTo(1)));
240 State state = updates == null ? null : updates.get(0);
241 assertThat(String.format("%s %s, state %s of type %s", item.getClass().getSimpleName(), itemName, state,
242 state == null ? null : state.getClass().getSimpleName()), state, is(matcher));
246 private void assertSingleStateUpdate(ModbusDataThingHandler handler, String channel, State state) {
247 assertSingleStateUpdate(handler, channel, is(equalTo(state)));
250 private void testOutOfBoundsGeneric(int pollStart, int pollLength, String start,
251 ModbusReadFunctionCode functionCode, ValueType valueType, ThingStatus expectedStatus) {
252 testOutOfBoundsGeneric(pollStart, pollLength, start, functionCode, valueType, expectedStatus, null);
255 private void testOutOfBoundsGeneric(int pollStart, int pollLength, String start,
256 ModbusReadFunctionCode functionCode, ValueType valueType, ThingStatus expectedStatus,
257 BundleContext context) {
258 ModbusSlaveEndpoint endpoint = new ModbusTCPSlaveEndpoint("thisishost", 502, false);
260 // Minimally mocked request
261 ModbusReadRequestBlueprint request = Mockito.mock(ModbusReadRequestBlueprint.class);
262 doReturn(pollStart).when(request).getReference();
263 doReturn(pollLength).when(request).getDataLength();
264 doReturn(functionCode).when(request).getFunctionCode();
266 PollTask task = Mockito.mock(PollTask.class);
267 doReturn(endpoint).when(task).getEndpoint();
268 doReturn(request).when(task).getRequest();
270 Bridge pollerThing = createPollerMock("poller1", task);
272 Configuration dataConfig = new Configuration();
273 dataConfig.put("readStart", start);
274 dataConfig.put("readTransform", "default");
275 dataConfig.put("readValueType", valueType.getConfigValue());
276 ModbusDataThingHandler dataHandler = createDataHandler("data1", pollerThing,
277 builder -> builder.withConfiguration(dataConfig), context);
278 assertThat(dataHandler.getThing().getStatusInfo().getDescription(), dataHandler.getThing().getStatus(),
279 is(equalTo(expectedStatus)));
283 public void testInitCoilsOutOfIndex() {
284 testOutOfBoundsGeneric(4, 3, "8", ModbusReadFunctionCode.READ_COILS, ModbusConstants.ValueType.BIT,
285 ThingStatus.OFFLINE);
289 public void testInitCoilsOutOfIndex2() {
290 // Reading coils 4, 5, 6. Coil 7 is out of bounds
291 testOutOfBoundsGeneric(4, 3, "7", ModbusReadFunctionCode.READ_COILS, ModbusConstants.ValueType.BIT,
292 ThingStatus.OFFLINE);
296 public void testInitCoilsOK() {
297 // Reading coils 4, 5, 6. Coil 6 is OK
298 testOutOfBoundsGeneric(4, 3, "6", ModbusReadFunctionCode.READ_COILS, ModbusConstants.ValueType.BIT,
303 public void testInitRegistersWithBitOutOfIndex() {
304 testOutOfBoundsGeneric(4, 3, "8.0", ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS,
305 ModbusConstants.ValueType.BIT, ThingStatus.OFFLINE);
309 public void testInitRegistersWithBitOutOfIndex2() {
310 testOutOfBoundsGeneric(4, 3, "7.16", ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS,
311 ModbusConstants.ValueType.BIT, ThingStatus.OFFLINE);
315 public void testInitRegistersWithBitOK() {
316 testOutOfBoundsGeneric(4, 3, "6.0", ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS,
317 ModbusConstants.ValueType.BIT, ThingStatus.ONLINE);
321 public void testInitRegistersWithBitOK2() {
322 testOutOfBoundsGeneric(4, 3, "6.15", ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS,
323 ModbusConstants.ValueType.BIT, ThingStatus.ONLINE);
327 public void testInitRegistersWithInt8OutOfIndex() {
328 testOutOfBoundsGeneric(4, 3, "8.0", ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS,
329 ModbusConstants.ValueType.INT8, ThingStatus.OFFLINE);
333 public void testInitRegistersWithInt8OutOfIndex2() {
334 testOutOfBoundsGeneric(4, 3, "7.2", ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS,
335 ModbusConstants.ValueType.INT8, ThingStatus.OFFLINE);
339 public void testInitRegistersWithInt8OK() {
340 testOutOfBoundsGeneric(4, 3, "6.0", ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS,
341 ModbusConstants.ValueType.INT8, ThingStatus.ONLINE);
345 public void testInitRegistersWithInt8OK2() {
346 testOutOfBoundsGeneric(4, 3, "6.1", ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS,
347 ModbusConstants.ValueType.INT8, ThingStatus.ONLINE);
351 public void testInitRegistersWithInt16OK() {
352 // Poller reading registers 4, 5, 6. Register 6 is OK
353 testOutOfBoundsGeneric(4, 3, "6", ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS,
354 ModbusConstants.ValueType.INT16, ThingStatus.ONLINE);
358 public void testInitRegistersWithInt16OutOfBounds() {
359 // Poller reading registers 4, 5, 6. Register 7 is out-of-bounds
360 testOutOfBoundsGeneric(4, 3, "7", ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS,
361 ModbusConstants.ValueType.INT16, ThingStatus.OFFLINE);
365 public void testInitRegistersWithInt16OutOfBounds2() {
366 testOutOfBoundsGeneric(4, 3, "8", ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS,
367 ModbusConstants.ValueType.INT16, ThingStatus.OFFLINE);
371 public void testInitRegistersWithInt16NoDecimalFormatAllowed() {
372 testOutOfBoundsGeneric(4, 3, "7.0", ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS,
373 ModbusConstants.ValueType.INT16, ThingStatus.OFFLINE);
377 public void testInitRegistersWithInt32OK() {
378 testOutOfBoundsGeneric(4, 3, "5", ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS,
379 ModbusConstants.ValueType.INT32, ThingStatus.ONLINE);
383 public void testInitRegistersWithInt32OutOfBounds() {
384 testOutOfBoundsGeneric(4, 3, "6", ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS,
385 ModbusConstants.ValueType.INT32, ThingStatus.OFFLINE);
389 public void testInitRegistersWithInt32AtTheEdge() {
390 testOutOfBoundsGeneric(4, 3, "5", ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS,
391 ModbusConstants.ValueType.INT32, ThingStatus.ONLINE);
394 private ModbusDataThingHandler testReadHandlingGeneric(ModbusReadFunctionCode functionCode, String start,
395 String transform, ValueType valueType, BitArray bits, ModbusRegisterArray registers, Exception error) {
396 return testReadHandlingGeneric(functionCode, start, transform, valueType, bits, registers, error, null);
399 private ModbusDataThingHandler testReadHandlingGeneric(ModbusReadFunctionCode functionCode, String start,
400 String transform, ValueType valueType, BitArray bits, ModbusRegisterArray registers, Exception error,
401 BundleContext context) {
402 return testReadHandlingGeneric(functionCode, start, transform, valueType, bits, registers, error, context,
406 @SuppressWarnings({ "null" })
407 private ModbusDataThingHandler testReadHandlingGeneric(ModbusReadFunctionCode functionCode, String start,
408 String transform, ValueType valueType, BitArray bits, ModbusRegisterArray registers, Exception error,
409 BundleContext context, boolean autoCreateItemsAndLinkToChannels) {
410 ModbusSlaveEndpoint endpoint = new ModbusTCPSlaveEndpoint("thisishost", 502, false);
414 // Minimally mocked request
415 ModbusReadRequestBlueprint request = Mockito.mock(ModbusReadRequestBlueprint.class);
416 doReturn(pollLength).when(request).getDataLength();
417 doReturn(functionCode).when(request).getFunctionCode();
419 PollTask task = Mockito.mock(PollTask.class);
420 doReturn(endpoint).when(task).getEndpoint();
421 doReturn(request).when(task).getRequest();
423 Bridge poller = createPollerMock("poller1", task);
425 Configuration dataConfig = new Configuration();
426 dataConfig.put("readStart", start);
427 dataConfig.put("readTransform", transform);
428 dataConfig.put("readValueType", valueType.getConfigValue());
430 String thingId = "read1";
431 ModbusDataThingHandler dataHandler = createDataHandler(thingId, poller,
432 builder -> builder.withConfiguration(dataConfig), context, autoCreateItemsAndLinkToChannels);
434 assertThat(dataHandler.getThing().getStatus(), is(equalTo(ThingStatus.ONLINE)));
438 assertNull(registers);
440 AsyncModbusReadResult result = new AsyncModbusReadResult(request, bits);
441 dataHandler.onReadResult(result);
442 } else if (registers != null) {
445 AsyncModbusReadResult result = new AsyncModbusReadResult(request, registers);
446 dataHandler.onReadResult(result);
449 assertNull(registers);
450 assertNotNull(error);
451 AsyncModbusFailure<ModbusReadRequestBlueprint> result = new AsyncModbusFailure<ModbusReadRequestBlueprint>(
453 dataHandler.handleReadError(result);
458 @SuppressWarnings({ "null" })
459 private ModbusDataThingHandler testWriteHandlingGeneric(String start, String transform, ValueType valueType,
460 String writeType, ModbusWriteFunctionCode successFC, String channel, Command command, Exception error,
461 BundleContext context) {
462 ModbusSlaveEndpoint endpoint = new ModbusTCPSlaveEndpoint("thisishost", 502, false);
464 // Minimally mocked request
465 ModbusReadRequestBlueprint request = Mockito.mock(ModbusReadRequestBlueprint.class);
467 PollTask task = Mockito.mock(PollTask.class);
468 doReturn(endpoint).when(task).getEndpoint();
469 doReturn(request).when(task).getRequest();
471 Bridge poller = createPollerMock("poller1", task);
473 Configuration dataConfig = new Configuration();
474 dataConfig.put("readStart", "");
475 dataConfig.put("writeStart", start);
476 dataConfig.put("writeTransform", transform);
477 dataConfig.put("writeValueType", valueType.getConfigValue());
478 dataConfig.put("writeType", writeType);
480 String thingId = "write";
482 ModbusDataThingHandler dataHandler = createDataHandler(thingId, poller,
483 builder -> builder.withConfiguration(dataConfig), context);
485 assertThat(dataHandler.getThing().getStatus(), is(equalTo(ThingStatus.ONLINE)));
487 dataHandler.handleCommand(new ChannelUID(dataHandler.getThing().getUID(), channel), command);
490 dataHandler.handleReadError(new AsyncModbusFailure<ModbusReadRequestBlueprint>(request, error));
492 ModbusResponse resp = new ModbusResponse() {
495 public int getFunctionCode() {
496 return successFC.getFunctionCode();
500 .onWriteResponse(new AsyncModbusWriteResult(Mockito.mock(ModbusWriteRequestBlueprint.class), resp));
506 public void testOnError() {
507 ModbusDataThingHandler dataHandler = testReadHandlingGeneric(ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS,
508 "0.0", "default", ModbusConstants.ValueType.BIT, null, null, new Exception("fooerror"));
510 assertSingleStateUpdate(dataHandler, CHANNEL_LAST_READ_ERROR, is(notNullValue(State.class)));
514 public void testOnRegistersInt16StaticTransformation() {
515 ModbusDataThingHandler dataHandler = testReadHandlingGeneric(ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS,
516 "0", "-3", ModbusConstants.ValueType.INT16, null,
517 new ModbusRegisterArray(new byte[] { (byte) 0xff, (byte) 0xfd }), null);
519 assertSingleStateUpdate(dataHandler, CHANNEL_LAST_READ_SUCCESS, is(notNullValue(State.class)));
520 assertSingleStateUpdate(dataHandler, CHANNEL_LAST_READ_ERROR, is(nullValue(State.class)));
522 // -3 converts to "true"
523 assertSingleStateUpdate(dataHandler, CHANNEL_CONTACT, is(nullValue(State.class)));
524 assertSingleStateUpdate(dataHandler, CHANNEL_SWITCH, is(nullValue(State.class)));
525 assertSingleStateUpdate(dataHandler, CHANNEL_DIMMER, is(nullValue(State.class)));
526 assertSingleStateUpdate(dataHandler, CHANNEL_NUMBER, new DecimalType(-3));
527 // roller shutter fails since -3 is invalid value (not between 0...100)
528 // assertThatStateContains(state, CHANNEL_ROLLERSHUTTER, new PercentType(1));
529 assertSingleStateUpdate(dataHandler, CHANNEL_STRING, new StringType("-3"));
530 // no datetime, conversion not possible without transformation
534 public void testOnRegistersRealTransformation() {
535 mockTransformation("MULTIPLY", new MultiplyTransformation());
536 ModbusDataThingHandler dataHandler = testReadHandlingGeneric(ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS,
537 "0", "MULTIPLY(10)", ModbusConstants.ValueType.INT16, null,
538 new ModbusRegisterArray(new byte[] { (byte) 0xff, (byte) 0xfd }), null, bundleContext);
540 assertSingleStateUpdate(dataHandler, CHANNEL_LAST_READ_SUCCESS, is(notNullValue(State.class)));
541 assertSingleStateUpdate(dataHandler, CHANNEL_LAST_READ_ERROR, is(nullValue(State.class)));
543 // transformation output (-30) is not valid for contact or switch
544 assertSingleStateUpdate(dataHandler, CHANNEL_CONTACT, is(nullValue(State.class)));
545 assertSingleStateUpdate(dataHandler, CHANNEL_SWITCH, is(nullValue(State.class)));
546 // -30 is not valid value for Dimmer (PercentType) (not between 0...100)
547 assertSingleStateUpdate(dataHandler, CHANNEL_DIMMER, is(nullValue(State.class)));
548 assertSingleStateUpdate(dataHandler, CHANNEL_NUMBER, new DecimalType(-30));
549 // roller shutter fails since -3 is invalid value (not between 0...100)
550 assertSingleStateUpdate(dataHandler, CHANNEL_ROLLERSHUTTER, is(nullValue(State.class)));
551 assertSingleStateUpdate(dataHandler, CHANNEL_STRING, new StringType("-30"));
552 // no datetime, conversion not possible without transformation
556 public void testOnRegistersNaNFloatInRegisters() throws InvalidSyntaxException {
557 ModbusDataThingHandler dataHandler = testReadHandlingGeneric(ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS,
558 "0", "default", ModbusConstants.ValueType.FLOAT32, null, new ModbusRegisterArray(
559 // equivalent of floating point NaN
560 new byte[] { (byte) 0x7f, (byte) 0xc0, (byte) 0x00, (byte) 0x00 }),
561 null, bundleContext);
563 assertSingleStateUpdate(dataHandler, CHANNEL_LAST_READ_SUCCESS, is(notNullValue(State.class)));
564 assertSingleStateUpdate(dataHandler, CHANNEL_LAST_READ_ERROR, is(nullValue(State.class)));
566 // UNDEF is treated as "boolean true" (OPEN/ON) since it is != 0.
567 assertSingleStateUpdate(dataHandler, CHANNEL_CONTACT, OpenClosedType.OPEN);
568 assertSingleStateUpdate(dataHandler, CHANNEL_SWITCH, OnOffType.ON);
569 assertSingleStateUpdate(dataHandler, CHANNEL_DIMMER, OnOffType.ON);
570 assertSingleStateUpdate(dataHandler, CHANNEL_NUMBER, UnDefType.UNDEF);
571 assertSingleStateUpdate(dataHandler, CHANNEL_ROLLERSHUTTER, UnDefType.UNDEF);
572 assertSingleStateUpdate(dataHandler, CHANNEL_STRING, UnDefType.UNDEF);
576 public void testOnRegistersRealTransformation2() throws InvalidSyntaxException {
577 mockTransformation("ONOFF", new TransformationService() {
580 public String transform(String function, String source) throws TransformationException {
581 return Integer.parseInt(source) != 0 ? "ON" : "OFF";
584 ModbusDataThingHandler dataHandler = testReadHandlingGeneric(ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS,
585 "0", "ONOFF(10)", ModbusConstants.ValueType.INT16, null,
586 new ModbusRegisterArray(new byte[] { (byte) 0xff, (byte) 0xfd }), null, bundleContext);
588 assertSingleStateUpdate(dataHandler, CHANNEL_LAST_READ_SUCCESS, is(notNullValue(State.class)));
589 assertSingleStateUpdate(dataHandler, CHANNEL_LAST_READ_ERROR, is(nullValue(State.class)));
591 assertSingleStateUpdate(dataHandler, CHANNEL_CONTACT, is(nullValue(State.class)));
592 assertSingleStateUpdate(dataHandler, CHANNEL_SWITCH, is(equalTo(OnOffType.ON)));
593 assertSingleStateUpdate(dataHandler, CHANNEL_DIMMER, is(equalTo(OnOffType.ON)));
594 assertSingleStateUpdate(dataHandler, CHANNEL_NUMBER, is(nullValue(State.class)));
595 assertSingleStateUpdate(dataHandler, CHANNEL_ROLLERSHUTTER, is(nullValue(State.class)));
596 assertSingleStateUpdate(dataHandler, CHANNEL_STRING, is(equalTo(new StringType("ON"))));
600 public void testWriteRealTransformation() throws InvalidSyntaxException {
601 captureModbusWrites();
602 mockTransformation("MULTIPLY", new MultiplyTransformation());
603 ModbusDataThingHandler dataHandler = testWriteHandlingGeneric("50", "MULTIPLY(10)",
604 ModbusConstants.ValueType.BIT, "coil", ModbusWriteFunctionCode.WRITE_COIL, "number",
605 new DecimalType("2"), null, bundleContext);
607 assertSingleStateUpdate(dataHandler, CHANNEL_LAST_WRITE_SUCCESS, is(notNullValue(State.class)));
608 assertSingleStateUpdate(dataHandler, CHANNEL_LAST_WRITE_ERROR, is(nullValue(State.class)));
609 assertThat(writeRequests.size(), is(equalTo(1)));
610 ModbusWriteRequestBlueprint writeRequest = writeRequests.get(0);
611 assertThat(writeRequest.getFunctionCode(), is(equalTo(ModbusWriteFunctionCode.WRITE_COIL)));
612 assertThat(writeRequest.getReference(), is(equalTo(50)));
613 assertThat(((ModbusWriteCoilRequestBlueprint) writeRequest).getCoils().size(), is(equalTo(1)));
614 // Since transform output is non-zero, it is mapped as "true"
615 assertThat(((ModbusWriteCoilRequestBlueprint) writeRequest).getCoils().getBit(0), is(equalTo(true)));
619 public void testWriteRealTransformation2() throws InvalidSyntaxException {
620 captureModbusWrites();
621 mockTransformation("ZERO", new TransformationService() {
624 public String transform(String function, String source) throws TransformationException {
628 ModbusDataThingHandler dataHandler = testWriteHandlingGeneric("50", "ZERO(foobar)",
629 ModbusConstants.ValueType.BIT, "coil", ModbusWriteFunctionCode.WRITE_COIL, "number",
630 new DecimalType("2"), null, bundleContext);
632 assertSingleStateUpdate(dataHandler, CHANNEL_LAST_WRITE_SUCCESS, is(notNullValue(State.class)));
633 assertSingleStateUpdate(dataHandler, CHANNEL_LAST_WRITE_ERROR, is(nullValue(State.class)));
634 assertThat(writeRequests.size(), is(equalTo(1)));
635 ModbusWriteRequestBlueprint writeRequest = writeRequests.get(0);
636 assertThat(writeRequest.getFunctionCode(), is(equalTo(ModbusWriteFunctionCode.WRITE_COIL)));
637 assertThat(writeRequest.getReference(), is(equalTo(50)));
638 assertThat(((ModbusWriteCoilRequestBlueprint) writeRequest).getCoils().size(), is(equalTo(1)));
639 // Since transform output is zero, it is mapped as "false"
640 assertThat(((ModbusWriteCoilRequestBlueprint) writeRequest).getCoils().getBit(0), is(equalTo(false)));
644 public void testWriteRealTransformation3() throws InvalidSyntaxException {
645 captureModbusWrites();
646 mockTransformation("RANDOM", new TransformationService() {
649 public String transform(String function, String source) throws TransformationException {
653 ModbusDataThingHandler dataHandler = testWriteHandlingGeneric("50", "RANDOM(foobar)",
654 ModbusConstants.ValueType.INT16, "holding", ModbusWriteFunctionCode.WRITE_SINGLE_REGISTER, "number",
655 new DecimalType("2"), null, bundleContext);
657 assertSingleStateUpdate(dataHandler, CHANNEL_LAST_WRITE_SUCCESS, is(notNullValue(State.class)));
658 assertSingleStateUpdate(dataHandler, CHANNEL_LAST_WRITE_ERROR, is(nullValue(State.class)));
659 assertThat(writeRequests.size(), is(equalTo(1)));
660 ModbusWriteRequestBlueprint writeRequest = writeRequests.get(0);
661 assertThat(writeRequest.getFunctionCode(), is(equalTo(ModbusWriteFunctionCode.WRITE_SINGLE_REGISTER)));
662 assertThat(writeRequest.getReference(), is(equalTo(50)));
663 assertThat(((ModbusWriteRegisterRequestBlueprint) writeRequest).getRegisters().size(), is(equalTo(1)));
664 assertThat(((ModbusWriteRegisterRequestBlueprint) writeRequest).getRegisters().getRegister(0), is(equalTo(5)));
668 public void testWriteRealTransformation4() throws InvalidSyntaxException {
669 captureModbusWrites();
670 mockTransformation("JSON", new TransformationService() {
673 public String transform(String function, String source) throws TransformationException {
675 + "\"functionCode\": 16,"//
676 + "\"address\": 5412,"//
677 + "\"value\": [1, 0, 5]"//
680 + "\"functionCode\": 6,"//
681 + "\"address\": 555,"//
686 ModbusDataThingHandler dataHandler = testWriteHandlingGeneric("50", "JSON(foobar)",
687 ModbusConstants.ValueType.INT16, "holding", ModbusWriteFunctionCode.WRITE_MULTIPLE_REGISTERS, "number",
688 new DecimalType("2"), null, bundleContext);
690 assertSingleStateUpdate(dataHandler, CHANNEL_LAST_WRITE_SUCCESS, is(notNullValue(State.class)));
691 assertSingleStateUpdate(dataHandler, CHANNEL_LAST_WRITE_ERROR, is(nullValue(State.class)));
692 assertThat(writeRequests.size(), is(equalTo(2)));
694 ModbusWriteRequestBlueprint writeRequest = writeRequests.get(0);
695 assertThat(writeRequest.getFunctionCode(), is(equalTo(ModbusWriteFunctionCode.WRITE_MULTIPLE_REGISTERS)));
696 assertThat(writeRequest.getReference(), is(equalTo(5412)));
697 assertThat(((ModbusWriteRegisterRequestBlueprint) writeRequest).getRegisters().size(), is(equalTo(3)));
698 assertThat(((ModbusWriteRegisterRequestBlueprint) writeRequest).getRegisters().getRegister(0),
700 assertThat(((ModbusWriteRegisterRequestBlueprint) writeRequest).getRegisters().getRegister(1),
702 assertThat(((ModbusWriteRegisterRequestBlueprint) writeRequest).getRegisters().getRegister(2),
706 ModbusWriteRequestBlueprint writeRequest = writeRequests.get(1);
707 assertThat(writeRequest.getFunctionCode(), is(equalTo(ModbusWriteFunctionCode.WRITE_SINGLE_REGISTER)));
708 assertThat(writeRequest.getReference(), is(equalTo(555)));
709 assertThat(((ModbusWriteRegisterRequestBlueprint) writeRequest).getRegisters().size(), is(equalTo(1)));
710 assertThat(((ModbusWriteRegisterRequestBlueprint) writeRequest).getRegisters().getRegister(0),
716 public void testWriteRealTransformation5() throws InvalidSyntaxException {
717 captureModbusWrites();
718 mockTransformation("PLUS", new TransformationService() {
721 public String transform(String arg, String source) throws TransformationException {
722 return String.valueOf(Integer.parseInt(arg) + Integer.parseInt(source));
725 mockTransformation("CONCAT", new TransformationService() {
728 public String transform(String function, String source) throws TransformationException {
729 return source + function;
732 mockTransformation("MULTIPLY", new MultiplyTransformation());
733 ModbusDataThingHandler dataHandler = testWriteHandlingGeneric("50", "MULTIPLY:3∩PLUS(2)∩CONCAT(0)",
734 ModbusConstants.ValueType.INT16, "holding", ModbusWriteFunctionCode.WRITE_SINGLE_REGISTER, "number",
735 new DecimalType("2"), null, bundleContext);
737 assertSingleStateUpdate(dataHandler, CHANNEL_LAST_WRITE_SUCCESS, is(notNullValue(State.class)));
738 assertSingleStateUpdate(dataHandler, CHANNEL_LAST_WRITE_ERROR, is(nullValue(State.class)));
739 assertThat(writeRequests.size(), is(equalTo(1)));
740 ModbusWriteRequestBlueprint writeRequest = writeRequests.get(0);
741 assertThat(writeRequest.getFunctionCode(), is(equalTo(ModbusWriteFunctionCode.WRITE_SINGLE_REGISTER)));
742 assertThat(writeRequest.getReference(), is(equalTo(50)));
743 assertThat(((ModbusWriteRegisterRequestBlueprint) writeRequest).getRegisters().size(), is(equalTo(1)));
744 assertThat(((ModbusWriteRegisterRequestBlueprint) writeRequest).getRegisters().getRegister(0),
745 is(equalTo(/* (2*3 + 2) + '0' */ 80)));
748 private void testValueTypeGeneric(ModbusReadFunctionCode functionCode, ValueType valueType,
749 ThingStatus expectedStatus) {
750 ModbusSlaveEndpoint endpoint = new ModbusTCPSlaveEndpoint("thisishost", 502, false);
752 // Minimally mocked request
753 ModbusReadRequestBlueprint request = Mockito.mock(ModbusReadRequestBlueprint.class);
754 doReturn(3).when(request).getDataLength();
755 doReturn(functionCode).when(request).getFunctionCode();
757 PollTask task = Mockito.mock(PollTask.class);
758 doReturn(endpoint).when(task).getEndpoint();
759 doReturn(request).when(task).getRequest();
761 Bridge poller = createPollerMock("poller1", task);
763 Configuration dataConfig = new Configuration();
764 dataConfig.put("readStart", "1");
765 dataConfig.put("readTransform", "default");
766 dataConfig.put("readValueType", valueType.getConfigValue());
767 ModbusDataThingHandler dataHandler = createDataHandler("data1", poller,
768 builder -> builder.withConfiguration(dataConfig));
769 assertThat(dataHandler.getThing().getStatus(), is(equalTo(expectedStatus)));
773 public void testCoilDoesNotAcceptFloat32ValueType() {
774 testValueTypeGeneric(ModbusReadFunctionCode.READ_COILS, ModbusConstants.ValueType.FLOAT32, ThingStatus.OFFLINE);
778 public void testCoilAcceptsBitValueType() {
779 testValueTypeGeneric(ModbusReadFunctionCode.READ_COILS, ModbusConstants.ValueType.BIT, ThingStatus.ONLINE);
783 public void testDiscreteInputDoesNotAcceptFloat32ValueType() {
784 testValueTypeGeneric(ModbusReadFunctionCode.READ_INPUT_DISCRETES, ModbusConstants.ValueType.FLOAT32,
785 ThingStatus.OFFLINE);
789 public void testDiscreteInputAcceptsBitValueType() {
790 testValueTypeGeneric(ModbusReadFunctionCode.READ_INPUT_DISCRETES, ModbusConstants.ValueType.BIT,
795 public void testRefreshOnData() throws InterruptedException {
796 ModbusReadFunctionCode functionCode = ModbusReadFunctionCode.READ_COILS;
798 ModbusSlaveEndpoint endpoint = new ModbusTCPSlaveEndpoint("thisishost", 502, false);
802 // Minimally mocked request
803 ModbusReadRequestBlueprint request = Mockito.mock(ModbusReadRequestBlueprint.class);
804 doReturn(pollLength).when(request).getDataLength();
805 doReturn(functionCode).when(request).getFunctionCode();
807 PollTask task = Mockito.mock(PollTask.class);
808 doReturn(endpoint).when(task).getEndpoint();
809 doReturn(request).when(task).getRequest();
811 Bridge poller = createPollerMock("poller1", task);
813 Configuration dataConfig = new Configuration();
814 dataConfig.put("readStart", "0");
815 dataConfig.put("readTransform", "default");
816 dataConfig.put("readValueType", "bit");
818 String thingId = "read1";
820 ModbusDataThingHandler dataHandler = createDataHandler(thingId, poller,
821 builder -> builder.withConfiguration(dataConfig), bundleContext);
822 assertThat(dataHandler.getThing().getStatus(), is(equalTo(ThingStatus.ONLINE)));
824 verify(comms, never()).submitOneTimePoll(eq(request), notNull(), notNull());
825 // Wait for all channels to receive the REFRESH command (initiated by the core)
827 () -> verify((ModbusPollerThingHandler) poller.getHandler(), times(CHANNEL_TO_ACCEPTED_TYPE.size()))
830 reset(poller.getHandler());
832 // Issue REFRESH command and verify the results
833 dataHandler.handleCommand(Mockito.mock(ChannelUID.class), RefreshType.REFRESH);
835 // data handler asynchronously calls the poller.refresh() -- it might take some time
836 // We check that refresh is finally called
837 waitForAssert(() -> verify((ModbusPollerThingHandler) poller.getHandler()).refresh());
842 * @param pollerFunctionCode poller function code. Use null if you want to have data thing direct child of endpoint
844 * @param config thing config
845 * @param statusConsumer assertion method for data thingstatus
847 private void testInitGeneric(ModbusReadFunctionCode pollerFunctionCode, Configuration config,
848 Consumer<ThingStatusInfo> statusConsumer) {
852 if (pollerFunctionCode == null) {
853 parent = createTcpMock();
856 ModbusSlaveEndpoint endpoint = new ModbusTCPSlaveEndpoint("thisishost", 502, false);
858 // Minimally mocked request
859 ModbusReadRequestBlueprint request = Mockito.mock(ModbusReadRequestBlueprint.class);
860 doReturn(pollLength).when(request).getDataLength();
861 doReturn(pollerFunctionCode).when(request).getFunctionCode();
863 PollTask task = Mockito.mock(PollTask.class);
864 doReturn(endpoint).when(task).getEndpoint();
865 doReturn(request).when(task).getRequest();
867 parent = createPollerMock("poller1", task);
870 String thingId = "read1";
872 ModbusDataThingHandler dataHandler = createDataHandler(thingId, parent,
873 builder -> builder.withConfiguration(config), bundleContext);
875 statusConsumer.accept(dataHandler.getThing().getStatusInfo());
879 public void testReadOnlyData() {
880 Configuration dataConfig = new Configuration();
881 dataConfig.put("readStart", "0");
882 dataConfig.put("readValueType", "bit");
883 testInitGeneric(ModbusReadFunctionCode.READ_COILS, dataConfig,
884 status -> assertThat(status.getStatus(), is(equalTo(ThingStatus.ONLINE))));
888 * readValueType=bit should be assumed with coils, so it's ok to skip it
891 public void testReadOnlyDataMissingValueTypeWithCoils() {
892 Configuration dataConfig = new Configuration();
893 dataConfig.put("readStart", "0");
894 // missing value type
895 testInitGeneric(ModbusReadFunctionCode.READ_COILS, dataConfig,
896 status -> assertThat(status.getStatus(), is(equalTo(ThingStatus.ONLINE))));
900 public void testReadOnlyDataInvalidValueType() {
901 Configuration dataConfig = new Configuration();
902 dataConfig.put("readStart", "0");
903 dataConfig.put("readValueType", "foobar");
904 testInitGeneric(ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS, dataConfig, status -> {
905 assertThat(status.getStatus(), is(equalTo(ThingStatus.OFFLINE)));
906 assertThat(status.getStatusDetail(), is(equalTo(ThingStatusDetail.CONFIGURATION_ERROR)));
911 * We do not assume value type with registers, not ok to skip it
914 public void testReadOnlyDataMissingValueTypeWithRegisters() {
915 Configuration dataConfig = new Configuration();
916 dataConfig.put("readStart", "0");
917 testInitGeneric(ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS, dataConfig, status -> {
918 assertThat(status.getStatus(), is(equalTo(ThingStatus.OFFLINE)));
919 assertThat(status.getStatusDetail(), is(equalTo(ThingStatusDetail.CONFIGURATION_ERROR)));
924 public void testWriteOnlyData() {
925 Configuration dataConfig = new Configuration();
926 dataConfig.put("writeStart", "0");
927 dataConfig.put("writeValueType", "bit");
928 dataConfig.put("writeType", "coil");
929 testInitGeneric(ModbusReadFunctionCode.READ_COILS, dataConfig,
930 status -> assertThat(status.getStatus(), is(equalTo(ThingStatus.ONLINE))));
934 public void testWriteHoldingInt16Data() {
935 Configuration dataConfig = new Configuration();
936 dataConfig.put("writeStart", "0");
937 dataConfig.put("writeValueType", "int16");
938 dataConfig.put("writeType", "holding");
939 testInitGeneric(ModbusReadFunctionCode.READ_COILS, dataConfig,
940 status -> assertThat(status.getStatus(), is(equalTo(ThingStatus.ONLINE))));
944 public void testWriteHoldingInt8Data() {
945 Configuration dataConfig = new Configuration();
946 dataConfig.put("writeStart", "0");
947 dataConfig.put("writeValueType", "int8");
948 dataConfig.put("writeType", "holding");
949 testInitGeneric(null, dataConfig, status -> {
950 assertThat(status.getStatus(), is(equalTo(ThingStatus.OFFLINE)));
951 assertThat(status.getStatusDetail(), is(equalTo(ThingStatusDetail.CONFIGURATION_ERROR)));
956 public void testWriteHoldingBitData() {
957 Configuration dataConfig = new Configuration();
958 dataConfig.put("writeStart", "0");
959 dataConfig.put("writeValueType", "bit");
960 dataConfig.put("writeType", "holding");
961 testInitGeneric(null, dataConfig, status -> {
962 assertThat(status.getStatus(), is(equalTo(ThingStatus.OFFLINE)));
963 assertThat(status.getStatusDetail(), is(equalTo(ThingStatusDetail.CONFIGURATION_ERROR)));
968 public void testWriteOnlyDataChildOfEndpoint() {
969 Configuration dataConfig = new Configuration();
970 dataConfig.put("writeStart", "0");
971 dataConfig.put("writeValueType", "bit");
972 dataConfig.put("writeType", "coil");
973 testInitGeneric(null, dataConfig, status -> assertThat(status.getStatus(), is(equalTo(ThingStatus.ONLINE))));
977 public void testWriteOnlyDataMissingOneParameter() {
978 Configuration dataConfig = new Configuration();
979 dataConfig.put("writeStart", "0");
980 dataConfig.put("writeValueType", "bit");
981 // missing writeType --> error
982 testInitGeneric(ModbusReadFunctionCode.READ_COILS, dataConfig, status -> {
983 assertThat(status.getStatus(), is(equalTo(ThingStatus.OFFLINE)));
984 assertThat(status.getStatusDetail(), is(equalTo(ThingStatusDetail.CONFIGURATION_ERROR)));
985 assertThat(status.getDescription(), is(not(equalTo(null))));
990 * OK to omit writeValueType with coils since bit is assumed
993 public void testWriteOnlyDataMissingValueTypeWithCoilParameter() {
994 Configuration dataConfig = new Configuration();
995 dataConfig.put("writeStart", "0");
996 dataConfig.put("writeType", "coil");
997 testInitGeneric(ModbusReadFunctionCode.READ_COILS, dataConfig,
998 status -> assertThat(status.getStatus(), is(equalTo(ThingStatus.ONLINE))));
1002 public void testWriteOnlyIllegalValueType() {
1003 Configuration dataConfig = new Configuration();
1004 dataConfig.put("writeStart", "0");
1005 dataConfig.put("writeType", "coil");
1006 dataConfig.put("writeValueType", "foobar");
1007 testInitGeneric(ModbusReadFunctionCode.READ_COILS, dataConfig, status -> {
1008 assertThat(status.getStatus(), is(equalTo(ThingStatus.OFFLINE)));
1009 assertThat(status.getStatusDetail(), is(equalTo(ThingStatusDetail.CONFIGURATION_ERROR)));
1014 public void testWriteInvalidType() {
1015 Configuration dataConfig = new Configuration();
1016 dataConfig.put("writeStart", "0");
1017 dataConfig.put("writeType", "foobar");
1018 testInitGeneric(ModbusReadFunctionCode.READ_COILS, dataConfig, status -> {
1019 assertThat(status.getStatus(), is(equalTo(ThingStatus.OFFLINE)));
1020 assertThat(status.getStatusDetail(), is(equalTo(ThingStatusDetail.CONFIGURATION_ERROR)));
1025 public void testWriteCoilBadStart() {
1026 Configuration dataConfig = new Configuration();
1027 dataConfig.put("writeStart", "0.4");
1028 dataConfig.put("writeType", "coil");
1029 testInitGeneric(null, dataConfig, status -> {
1030 assertThat(status.getStatus(), is(equalTo(ThingStatus.OFFLINE)));
1031 assertThat(status.getStatusDetail(), is(equalTo(ThingStatusDetail.CONFIGURATION_ERROR)));
1036 public void testWriteHoldingBadStart() {
1037 Configuration dataConfig = new Configuration();
1038 dataConfig.put("writeStart", "0.4");
1039 dataConfig.put("writeType", "holding");
1040 testInitGeneric(null, dataConfig, status -> {
1041 assertThat(status.getStatus(), is(equalTo(ThingStatus.OFFLINE)));
1042 assertThat(status.getStatusDetail(), is(equalTo(ThingStatusDetail.CONFIGURATION_ERROR)));
1047 public void testReadHoldingBadStart() {
1048 Configuration dataConfig = new Configuration();
1049 dataConfig.put("readStart", "0.0");
1050 dataConfig.put("readValueType", "int16");
1051 testInitGeneric(ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS, dataConfig, status -> {
1052 assertThat(status.getStatus(), is(equalTo(ThingStatus.OFFLINE)));
1053 assertThat(status.getStatusDetail(), is(equalTo(ThingStatusDetail.CONFIGURATION_ERROR)));
1058 public void testReadHoldingBadStart2() {
1059 Configuration dataConfig = new Configuration();
1060 dataConfig.put("readStart", "0.0");
1061 dataConfig.put("readValueType", "bit");
1062 testInitGeneric(ModbusReadFunctionCode.READ_COILS, dataConfig, status -> {
1063 assertThat(status.getStatus(), is(equalTo(ThingStatus.OFFLINE)));
1064 assertThat(status.getStatusDetail(), is(equalTo(ThingStatusDetail.CONFIGURATION_ERROR)));
1069 public void testReadHoldingOKStart() {
1070 Configuration dataConfig = new Configuration();
1071 dataConfig.put("readStart", "0.0");
1072 dataConfig.put("readType", "holding");
1073 dataConfig.put("readValueType", "bit");
1074 testInitGeneric(ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS, dataConfig,
1075 status -> assertThat(status.getStatus(), is(equalTo(ThingStatus.ONLINE))));
1079 public void testReadValueTypeIllegal() {
1080 Configuration dataConfig = new Configuration();
1081 dataConfig.put("readStart", "0.0");
1082 dataConfig.put("readType", "holding");
1083 dataConfig.put("readValueType", "foobar");
1084 testInitGeneric(ModbusReadFunctionCode.READ_COILS, dataConfig, status -> {
1085 assertThat(status.getStatus(), is(equalTo(ThingStatus.OFFLINE)));
1086 assertThat(status.getStatusDetail(), is(equalTo(ThingStatusDetail.CONFIGURATION_ERROR)));
1091 public void testWriteOnlyTransform() {
1092 Configuration dataConfig = new Configuration();
1093 // no need to have start, JSON output of transformation defines everything
1094 dataConfig.put("writeTransform", "JS(myJsonTransform.js)");
1095 testInitGeneric(null, dataConfig, status -> assertThat(status.getStatus(), is(equalTo(ThingStatus.ONLINE))));
1099 public void testWriteTransformAndStart() {
1100 Configuration dataConfig = new Configuration();
1101 // It's illegal to have start and transform. Just have transform or have all
1102 dataConfig.put("writeStart", "3");
1103 dataConfig.put("writeTransform", "JS(myJsonTransform.js)");
1104 testInitGeneric(ModbusReadFunctionCode.READ_COILS, dataConfig, status -> {
1105 assertThat(status.getStatus(), is(equalTo(ThingStatus.OFFLINE)));
1106 assertThat(status.getStatusDetail(), is(equalTo(ThingStatusDetail.CONFIGURATION_ERROR)));
1111 public void testWriteTransformAndNecessary() {
1112 Configuration dataConfig = new Configuration();
1113 // It's illegal to have start and transform. Just have transform or have all
1114 dataConfig.put("writeStart", "3");
1115 dataConfig.put("writeType", "holding");
1116 dataConfig.put("writeValueType", "int16");
1117 dataConfig.put("writeTransform", "JS(myJsonTransform.js)");
1118 testInitGeneric(null, dataConfig, status -> assertThat(status.getStatus(), is(equalTo(ThingStatus.ONLINE))));