2 * Copyright (c) 2010-2020 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.modbus.tests;
15 import static org.hamcrest.CoreMatchers.*;
16 import static org.hamcrest.MatcherAssert.assertThat;
17 import static org.junit.jupiter.api.Assertions.*;
18 import static org.mockito.ArgumentMatchers.*;
19 import static org.mockito.ArgumentMatchers.any;
20 import static org.mockito.Mockito.*;
21 import static org.openhab.binding.modbus.internal.ModbusBindingConstantsInternal.*;
24 import java.util.Map.Entry;
25 import java.util.concurrent.ScheduledFuture;
26 import java.util.function.Consumer;
27 import java.util.function.Function;
29 import org.apache.commons.lang.StringUtils;
30 import org.hamcrest.Matcher;
31 import org.junit.jupiter.api.AfterEach;
32 import org.junit.jupiter.api.Test;
33 import org.mockito.Mockito;
34 import org.openhab.binding.modbus.handler.EndpointNotInitializedException;
35 import org.openhab.binding.modbus.handler.ModbusPollerThingHandler;
36 import org.openhab.binding.modbus.internal.handler.ModbusDataThingHandler;
37 import org.openhab.binding.modbus.internal.handler.ModbusTcpThingHandler;
38 import org.openhab.core.config.core.Configuration;
39 import org.openhab.core.items.GenericItem;
40 import org.openhab.core.items.Item;
41 import org.openhab.core.library.items.DateTimeItem;
42 import org.openhab.core.library.types.DecimalType;
43 import org.openhab.core.library.types.OnOffType;
44 import org.openhab.core.library.types.OpenClosedType;
45 import org.openhab.core.library.types.StringType;
46 import org.openhab.core.thing.Bridge;
47 import org.openhab.core.thing.ChannelUID;
48 import org.openhab.core.thing.Thing;
49 import org.openhab.core.thing.ThingStatus;
50 import org.openhab.core.thing.ThingStatusDetail;
51 import org.openhab.core.thing.ThingStatusInfo;
52 import org.openhab.core.thing.ThingUID;
53 import org.openhab.core.thing.binding.ThingHandler;
54 import org.openhab.core.thing.binding.builder.BridgeBuilder;
55 import org.openhab.core.thing.binding.builder.ChannelBuilder;
56 import org.openhab.core.thing.binding.builder.ThingBuilder;
57 import org.openhab.core.transform.TransformationException;
58 import org.openhab.core.transform.TransformationService;
59 import org.openhab.core.types.Command;
60 import org.openhab.core.types.RefreshType;
61 import org.openhab.core.types.State;
62 import org.openhab.core.types.UnDefType;
63 import org.openhab.io.transport.modbus.AsyncModbusFailure;
64 import org.openhab.io.transport.modbus.AsyncModbusReadResult;
65 import org.openhab.io.transport.modbus.AsyncModbusWriteResult;
66 import org.openhab.io.transport.modbus.BitArray;
67 import org.openhab.io.transport.modbus.ModbusConstants;
68 import org.openhab.io.transport.modbus.ModbusConstants.ValueType;
69 import org.openhab.io.transport.modbus.ModbusReadFunctionCode;
70 import org.openhab.io.transport.modbus.ModbusReadRequestBlueprint;
71 import org.openhab.io.transport.modbus.ModbusRegisterArray;
72 import org.openhab.io.transport.modbus.ModbusResponse;
73 import org.openhab.io.transport.modbus.ModbusWriteCoilRequestBlueprint;
74 import org.openhab.io.transport.modbus.ModbusWriteFunctionCode;
75 import org.openhab.io.transport.modbus.ModbusWriteRegisterRequestBlueprint;
76 import org.openhab.io.transport.modbus.ModbusWriteRequestBlueprint;
77 import org.openhab.io.transport.modbus.PollTask;
78 import org.openhab.io.transport.modbus.endpoint.ModbusSlaveEndpoint;
79 import org.openhab.io.transport.modbus.endpoint.ModbusTCPSlaveEndpoint;
80 import org.osgi.framework.BundleContext;
81 import org.osgi.framework.InvalidSyntaxException;
84 * @author Sami Salonen - Initial contribution
86 public class ModbusDataHandlerTest extends AbstractModbusOSGiTest {
88 private final class MultiplyTransformation implements TransformationService {
90 public String transform(String function, String source) throws TransformationException {
91 return String.valueOf(Integer.parseInt(function) * Integer.parseInt(source));
95 private static final Map<String, String> CHANNEL_TO_ACCEPTED_TYPE = new HashMap<>();
97 CHANNEL_TO_ACCEPTED_TYPE.put(CHANNEL_SWITCH, "Switch");
98 CHANNEL_TO_ACCEPTED_TYPE.put(CHANNEL_CONTACT, "Contact");
99 CHANNEL_TO_ACCEPTED_TYPE.put(CHANNEL_DATETIME, "DateTime");
100 CHANNEL_TO_ACCEPTED_TYPE.put(CHANNEL_DIMMER, "Dimmer");
101 CHANNEL_TO_ACCEPTED_TYPE.put(CHANNEL_NUMBER, "Number");
102 CHANNEL_TO_ACCEPTED_TYPE.put(CHANNEL_STRING, "String");
103 CHANNEL_TO_ACCEPTED_TYPE.put(CHANNEL_ROLLERSHUTTER, "Rollershutter");
104 CHANNEL_TO_ACCEPTED_TYPE.put(CHANNEL_LAST_READ_SUCCESS, "DateTime");
105 CHANNEL_TO_ACCEPTED_TYPE.put(CHANNEL_LAST_WRITE_SUCCESS, "DateTime");
106 CHANNEL_TO_ACCEPTED_TYPE.put(CHANNEL_LAST_WRITE_ERROR, "DateTime");
107 CHANNEL_TO_ACCEPTED_TYPE.put(CHANNEL_LAST_READ_ERROR, "DateTime");
109 private List<ModbusWriteRequestBlueprint> writeRequests = new ArrayList<>();
112 public void tearDown() {
113 writeRequests.clear();
116 private void captureModbusWrites() {
117 Mockito.when(comms.submitOneTimeWrite(any(), any(), any())).then(invocation -> {
118 ModbusWriteRequestBlueprint task = (ModbusWriteRequestBlueprint) invocation.getArgument(0);
119 writeRequests.add(task);
120 return Mockito.mock(ScheduledFuture.class);
124 private Bridge createPollerMock(String pollerId, PollTask task) {
126 ThingUID thingUID = new ThingUID(THING_TYPE_MODBUS_POLLER, pollerId);
127 BridgeBuilder builder = BridgeBuilder.create(THING_TYPE_MODBUS_POLLER, thingUID)
128 .withLabel("label for " + pollerId);
129 for (Entry<String, String> entry : CHANNEL_TO_ACCEPTED_TYPE.entrySet()) {
130 String channelId = entry.getKey();
131 String channelAcceptedType = entry.getValue();
132 builder = builder.withChannel(
133 ChannelBuilder.create(new ChannelUID(thingUID, channelId), channelAcceptedType).build());
135 poller = builder.build();
136 poller.setStatusInfo(new ThingStatusInfo(ThingStatus.ONLINE, ThingStatusDetail.NONE, ""));
138 ModbusPollerThingHandler mockHandler = Mockito.mock(ModbusPollerThingHandler.class);
139 doReturn(task.getRequest()).when(mockHandler).getRequest();
140 assert comms != null;
141 doReturn(comms).when(mockHandler).getCommunicationInterface();
142 doReturn(task.getEndpoint()).when(comms).getEndpoint();
143 poller.setHandler(mockHandler);
144 assertSame(poller.getHandler(), mockHandler);
145 assertSame(((ModbusPollerThingHandler) poller.getHandler()).getCommunicationInterface().getEndpoint(),
147 assertSame(((ModbusPollerThingHandler) poller.getHandler()).getRequest(), task.getRequest());
153 private Bridge createTcpMock() {
154 ModbusSlaveEndpoint endpoint = new ModbusTCPSlaveEndpoint("thisishost", 502);
155 Bridge tcpBridge = ModbusPollerThingHandlerTest.createTcpThingBuilder("tcp1").build();
156 ModbusTcpThingHandler tcpThingHandler = Mockito.mock(ModbusTcpThingHandler.class);
157 tcpBridge.setStatusInfo(new ThingStatusInfo(ThingStatus.ONLINE, ThingStatusDetail.NONE, ""));
158 tcpBridge.setHandler(tcpThingHandler);
159 doReturn(comms).when(tcpThingHandler).getCommunicationInterface();
161 doReturn(0).when(tcpThingHandler).getSlaveId();
162 } catch (EndpointNotInitializedException e) {
163 // not raised -- we are mocking return value only, not actually calling the method
164 throw new IllegalStateException();
166 tcpThingHandler.initialize();
167 assertThat(tcpBridge.getStatus(), is(equalTo(ThingStatus.ONLINE)));
171 private ModbusDataThingHandler createDataHandler(String id, Bridge bridge,
172 Function<ThingBuilder, ThingBuilder> builderConfigurator) {
173 return createDataHandler(id, bridge, builderConfigurator, null);
176 private ModbusDataThingHandler createDataHandler(String id, Bridge bridge,
177 Function<ThingBuilder, ThingBuilder> builderConfigurator, BundleContext context) {
178 return createDataHandler(id, bridge, builderConfigurator, context, true);
181 private ModbusDataThingHandler createDataHandler(String id, Bridge bridge,
182 Function<ThingBuilder, ThingBuilder> builderConfigurator, BundleContext context,
183 boolean autoCreateItemsAndLinkToChannels) {
184 ThingUID thingUID = new ThingUID(THING_TYPE_MODBUS_DATA, id);
185 ThingBuilder builder = ThingBuilder.create(THING_TYPE_MODBUS_DATA, thingUID).withLabel("label for " + id);
186 Map<String, ChannelUID> toBeLinked = new HashMap<>();
187 for (Entry<String, String> entry : CHANNEL_TO_ACCEPTED_TYPE.entrySet()) {
188 String channelId = entry.getKey();
189 String channelAcceptedType = entry.getValue();
190 ChannelUID channelUID = new ChannelUID(thingUID, channelId);
191 builder = builder.withChannel(ChannelBuilder.create(channelUID, channelAcceptedType).build());
193 if (autoCreateItemsAndLinkToChannels) {
194 // Create item of correct type and link it to channel
195 String itemName = getItemName(channelUID);
196 final GenericItem item;
197 if (channelId.startsWith("last") || channelId.equals("datetime")) {
198 item = new DateTimeItem(itemName);
200 item = coreItemFactory.createItem(StringUtils.capitalize(channelId), 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);
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);
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);
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),
717 private void testValueTypeGeneric(ModbusReadFunctionCode functionCode, ValueType valueType,
718 ThingStatus expectedStatus) {
719 ModbusSlaveEndpoint endpoint = new ModbusTCPSlaveEndpoint("thisishost", 502);
721 // Minimally mocked request
722 ModbusReadRequestBlueprint request = Mockito.mock(ModbusReadRequestBlueprint.class);
723 doReturn(3).when(request).getDataLength();
724 doReturn(functionCode).when(request).getFunctionCode();
726 PollTask task = Mockito.mock(PollTask.class);
727 doReturn(endpoint).when(task).getEndpoint();
728 doReturn(request).when(task).getRequest();
730 Bridge poller = createPollerMock("poller1", task);
732 Configuration dataConfig = new Configuration();
733 dataConfig.put("readStart", "1");
734 dataConfig.put("readTransform", "default");
735 dataConfig.put("readValueType", valueType.getConfigValue());
736 ModbusDataThingHandler dataHandler = createDataHandler("data1", poller,
737 builder -> builder.withConfiguration(dataConfig));
738 assertThat(dataHandler.getThing().getStatus(), is(equalTo(expectedStatus)));
742 public void testCoilDoesNotAcceptFloat32ValueType() {
743 testValueTypeGeneric(ModbusReadFunctionCode.READ_COILS, ModbusConstants.ValueType.FLOAT32, ThingStatus.OFFLINE);
747 public void testCoilAcceptsBitValueType() {
748 testValueTypeGeneric(ModbusReadFunctionCode.READ_COILS, ModbusConstants.ValueType.BIT, ThingStatus.ONLINE);
752 public void testDiscreteInputDoesNotAcceptFloat32ValueType() {
753 testValueTypeGeneric(ModbusReadFunctionCode.READ_INPUT_DISCRETES, ModbusConstants.ValueType.FLOAT32,
754 ThingStatus.OFFLINE);
758 public void testDiscreteInputAcceptsBitValueType() {
759 testValueTypeGeneric(ModbusReadFunctionCode.READ_INPUT_DISCRETES, ModbusConstants.ValueType.BIT,
764 public void testRefreshOnData() throws InterruptedException {
765 ModbusReadFunctionCode functionCode = ModbusReadFunctionCode.READ_COILS;
767 ModbusSlaveEndpoint endpoint = new ModbusTCPSlaveEndpoint("thisishost", 502);
771 // Minimally mocked request
772 ModbusReadRequestBlueprint request = Mockito.mock(ModbusReadRequestBlueprint.class);
773 doReturn(pollLength).when(request).getDataLength();
774 doReturn(functionCode).when(request).getFunctionCode();
776 PollTask task = Mockito.mock(PollTask.class);
777 doReturn(endpoint).when(task).getEndpoint();
778 doReturn(request).when(task).getRequest();
780 Bridge poller = createPollerMock("poller1", task);
782 Configuration dataConfig = new Configuration();
783 dataConfig.put("readStart", "0");
784 dataConfig.put("readTransform", "default");
785 dataConfig.put("readValueType", "bit");
787 String thingId = "read1";
789 ModbusDataThingHandler dataHandler = createDataHandler(thingId, poller,
790 builder -> builder.withConfiguration(dataConfig), bundleContext);
791 assertThat(dataHandler.getThing().getStatus(), is(equalTo(ThingStatus.ONLINE)));
793 verify(comms, never()).submitOneTimePoll(eq(request), notNull(), notNull());
794 // Reset initial REFRESH commands to data thing channels from the Core
795 reset(poller.getHandler());
796 dataHandler.handleCommand(Mockito.mock(ChannelUID.class), RefreshType.REFRESH);
798 // data handler asynchronously calls the poller.refresh() -- it might take some time
799 // We check that refresh is finally called
800 waitForAssert(() -> verify((ModbusPollerThingHandler) poller.getHandler()).refresh(), 2500, 50);
805 * @param pollerFunctionCode poller function code. Use null if you want to have data thing direct child of endpoint
807 * @param config thing config
808 * @param statusConsumer assertion method for data thingstatus
810 private void testInitGeneric(ModbusReadFunctionCode pollerFunctionCode, Configuration config,
811 Consumer<ThingStatusInfo> statusConsumer) {
815 if (pollerFunctionCode == null) {
816 parent = createTcpMock();
817 ThingHandler foo = parent.getHandler();
820 ModbusSlaveEndpoint endpoint = new ModbusTCPSlaveEndpoint("thisishost", 502);
822 // Minimally mocked request
823 ModbusReadRequestBlueprint request = Mockito.mock(ModbusReadRequestBlueprint.class);
824 doReturn(pollLength).when(request).getDataLength();
825 doReturn(pollerFunctionCode).when(request).getFunctionCode();
827 PollTask task = Mockito.mock(PollTask.class);
828 doReturn(endpoint).when(task).getEndpoint();
829 doReturn(request).when(task).getRequest();
831 parent = createPollerMock("poller1", task);
834 String thingId = "read1";
836 ModbusDataThingHandler dataHandler = createDataHandler(thingId, parent,
837 builder -> builder.withConfiguration(config), bundleContext);
839 statusConsumer.accept(dataHandler.getThing().getStatusInfo());
843 public void testReadOnlyData() {
844 Configuration dataConfig = new Configuration();
845 dataConfig.put("readStart", "0");
846 dataConfig.put("readValueType", "bit");
847 testInitGeneric(ModbusReadFunctionCode.READ_COILS, dataConfig,
848 status -> assertThat(status.getStatus(), is(equalTo(ThingStatus.ONLINE))));
852 * readValueType=bit should be assumed with coils, so it's ok to skip it
855 public void testReadOnlyDataMissingValueTypeWithCoils() {
856 Configuration dataConfig = new Configuration();
857 dataConfig.put("readStart", "0");
858 // missing value type
859 testInitGeneric(ModbusReadFunctionCode.READ_COILS, dataConfig,
860 status -> assertThat(status.getStatus(), is(equalTo(ThingStatus.ONLINE))));
864 public void testReadOnlyDataInvalidValueType() {
865 Configuration dataConfig = new Configuration();
866 dataConfig.put("readStart", "0");
867 dataConfig.put("readValueType", "foobar");
868 testInitGeneric(ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS, dataConfig, status -> {
869 assertThat(status.getStatus(), is(equalTo(ThingStatus.OFFLINE)));
870 assertThat(status.getStatusDetail(), is(equalTo(ThingStatusDetail.CONFIGURATION_ERROR)));
875 * We do not assume value type with registers, not ok to skip it
878 public void testReadOnlyDataMissingValueTypeWithRegisters() {
879 Configuration dataConfig = new Configuration();
880 dataConfig.put("readStart", "0");
881 testInitGeneric(ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS, dataConfig, status -> {
882 assertThat(status.getStatus(), is(equalTo(ThingStatus.OFFLINE)));
883 assertThat(status.getStatusDetail(), is(equalTo(ThingStatusDetail.CONFIGURATION_ERROR)));
888 public void testWriteOnlyData() {
889 Configuration dataConfig = new Configuration();
890 dataConfig.put("writeStart", "0");
891 dataConfig.put("writeValueType", "bit");
892 dataConfig.put("writeType", "coil");
893 testInitGeneric(ModbusReadFunctionCode.READ_COILS, dataConfig,
894 status -> assertThat(status.getStatus(), is(equalTo(ThingStatus.ONLINE))));
898 public void testWriteHoldingInt16Data() {
899 Configuration dataConfig = new Configuration();
900 dataConfig.put("writeStart", "0");
901 dataConfig.put("writeValueType", "int16");
902 dataConfig.put("writeType", "holding");
903 testInitGeneric(ModbusReadFunctionCode.READ_COILS, dataConfig,
904 status -> assertThat(status.getStatus(), is(equalTo(ThingStatus.ONLINE))));
908 public void testWriteHoldingInt8Data() {
909 Configuration dataConfig = new Configuration();
910 dataConfig.put("writeStart", "0");
911 dataConfig.put("writeValueType", "int8");
912 dataConfig.put("writeType", "holding");
913 testInitGeneric(null, dataConfig, status -> {
914 assertThat(status.getStatus(), is(equalTo(ThingStatus.OFFLINE)));
915 assertThat(status.getStatusDetail(), is(equalTo(ThingStatusDetail.CONFIGURATION_ERROR)));
920 public void testWriteHoldingBitData() {
921 Configuration dataConfig = new Configuration();
922 dataConfig.put("writeStart", "0");
923 dataConfig.put("writeValueType", "bit");
924 dataConfig.put("writeType", "holding");
925 testInitGeneric(null, dataConfig, status -> {
926 assertThat(status.getStatus(), is(equalTo(ThingStatus.OFFLINE)));
927 assertThat(status.getStatusDetail(), is(equalTo(ThingStatusDetail.CONFIGURATION_ERROR)));
932 public void testWriteOnlyDataChildOfEndpoint() {
933 Configuration dataConfig = new Configuration();
934 dataConfig.put("writeStart", "0");
935 dataConfig.put("writeValueType", "bit");
936 dataConfig.put("writeType", "coil");
937 testInitGeneric(null, dataConfig, status -> assertThat(status.getStatus(), is(equalTo(ThingStatus.ONLINE))));
941 public void testWriteOnlyDataMissingOneParameter() {
942 Configuration dataConfig = new Configuration();
943 dataConfig.put("writeStart", "0");
944 dataConfig.put("writeValueType", "bit");
945 // missing writeType --> error
946 testInitGeneric(ModbusReadFunctionCode.READ_COILS, dataConfig, status -> {
947 assertThat(status.getStatus(), is(equalTo(ThingStatus.OFFLINE)));
948 assertThat(status.getStatusDetail(), is(equalTo(ThingStatusDetail.CONFIGURATION_ERROR)));
949 assertThat(status.getDescription(), is(not(equalTo(null))));
954 * OK to omit writeValueType with coils since bit is assumed
957 public void testWriteOnlyDataMissingValueTypeWithCoilParameter() {
958 Configuration dataConfig = new Configuration();
959 dataConfig.put("writeStart", "0");
960 dataConfig.put("writeType", "coil");
961 testInitGeneric(ModbusReadFunctionCode.READ_COILS, dataConfig,
962 status -> assertThat(status.getStatus(), is(equalTo(ThingStatus.ONLINE))));
966 public void testWriteOnlyIllegalValueType() {
967 Configuration dataConfig = new Configuration();
968 dataConfig.put("writeStart", "0");
969 dataConfig.put("writeType", "coil");
970 dataConfig.put("writeValueType", "foobar");
971 testInitGeneric(ModbusReadFunctionCode.READ_COILS, dataConfig, status -> {
972 assertThat(status.getStatus(), is(equalTo(ThingStatus.OFFLINE)));
973 assertThat(status.getStatusDetail(), is(equalTo(ThingStatusDetail.CONFIGURATION_ERROR)));
978 public void testWriteInvalidType() {
979 Configuration dataConfig = new Configuration();
980 dataConfig.put("writeStart", "0");
981 dataConfig.put("writeType", "foobar");
982 testInitGeneric(ModbusReadFunctionCode.READ_COILS, dataConfig, status -> {
983 assertThat(status.getStatus(), is(equalTo(ThingStatus.OFFLINE)));
984 assertThat(status.getStatusDetail(), is(equalTo(ThingStatusDetail.CONFIGURATION_ERROR)));
989 public void testWriteCoilBadStart() {
990 Configuration dataConfig = new Configuration();
991 dataConfig.put("writeStart", "0.4");
992 dataConfig.put("writeType", "coil");
993 testInitGeneric(null, dataConfig, status -> {
994 assertThat(status.getStatus(), is(equalTo(ThingStatus.OFFLINE)));
995 assertThat(status.getStatusDetail(), is(equalTo(ThingStatusDetail.CONFIGURATION_ERROR)));
1000 public void testWriteHoldingBadStart() {
1001 Configuration dataConfig = new Configuration();
1002 dataConfig.put("writeStart", "0.4");
1003 dataConfig.put("writeType", "holding");
1004 testInitGeneric(null, dataConfig, status -> {
1005 assertThat(status.getStatus(), is(equalTo(ThingStatus.OFFLINE)));
1006 assertThat(status.getStatusDetail(), is(equalTo(ThingStatusDetail.CONFIGURATION_ERROR)));
1011 public void testReadHoldingBadStart() {
1012 Configuration dataConfig = new Configuration();
1013 dataConfig.put("readStart", "0.0");
1014 dataConfig.put("readValueType", "int16");
1015 testInitGeneric(ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS, dataConfig, status -> {
1016 assertThat(status.getStatus(), is(equalTo(ThingStatus.OFFLINE)));
1017 assertThat(status.getStatusDetail(), is(equalTo(ThingStatusDetail.CONFIGURATION_ERROR)));
1022 public void testReadHoldingBadStart2() {
1023 Configuration dataConfig = new Configuration();
1024 dataConfig.put("readStart", "0.0");
1025 dataConfig.put("readValueType", "bit");
1026 testInitGeneric(ModbusReadFunctionCode.READ_COILS, dataConfig, status -> {
1027 assertThat(status.getStatus(), is(equalTo(ThingStatus.OFFLINE)));
1028 assertThat(status.getStatusDetail(), is(equalTo(ThingStatusDetail.CONFIGURATION_ERROR)));
1033 public void testReadHoldingOKStart() {
1034 Configuration dataConfig = new Configuration();
1035 dataConfig.put("readStart", "0.0");
1036 dataConfig.put("readType", "holding");
1037 dataConfig.put("readValueType", "bit");
1038 testInitGeneric(ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS, dataConfig,
1039 status -> assertThat(status.getStatus(), is(equalTo(ThingStatus.ONLINE))));
1043 public void testReadValueTypeIllegal() {
1044 Configuration dataConfig = new Configuration();
1045 dataConfig.put("readStart", "0.0");
1046 dataConfig.put("readType", "holding");
1047 dataConfig.put("readValueType", "foobar");
1048 testInitGeneric(ModbusReadFunctionCode.READ_COILS, dataConfig, status -> {
1049 assertThat(status.getStatus(), is(equalTo(ThingStatus.OFFLINE)));
1050 assertThat(status.getStatusDetail(), is(equalTo(ThingStatusDetail.CONFIGURATION_ERROR)));
1055 public void testWriteOnlyTransform() {
1056 Configuration dataConfig = new Configuration();
1057 // no need to have start, JSON output of transformation defines everything
1058 dataConfig.put("writeTransform", "JS(myJsonTransform.js)");
1059 testInitGeneric(null, dataConfig, status -> assertThat(status.getStatus(), is(equalTo(ThingStatus.ONLINE))));
1063 public void testWriteTransformAndStart() {
1064 Configuration dataConfig = new Configuration();
1065 // It's illegal to have start and transform. Just have transform or have all
1066 dataConfig.put("writeStart", "3");
1067 dataConfig.put("writeTransform", "JS(myJsonTransform.js)");
1068 testInitGeneric(ModbusReadFunctionCode.READ_COILS, dataConfig, status -> {
1069 assertThat(status.getStatus(), is(equalTo(ThingStatus.OFFLINE)));
1070 assertThat(status.getStatusDetail(), is(equalTo(ThingStatusDetail.CONFIGURATION_ERROR)));
1075 public void testWriteTransformAndNecessary() {
1076 Configuration dataConfig = new Configuration();
1077 // It's illegal to have start and transform. Just have transform or have all
1078 dataConfig.put("writeStart", "3");
1079 dataConfig.put("writeType", "holding");
1080 dataConfig.put("writeValueType", "int16");
1081 dataConfig.put("writeTransform", "JS(myJsonTransform.js)");
1082 testInitGeneric(null, dataConfig, status -> assertThat(status.getStatus(), is(equalTo(ThingStatus.ONLINE))));