/** * Copyright (c) 2010-2017 by the respective copyright holders. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html */ package org.openhab.binding.modbus.internal; import static org.hamcrest.CoreMatchers.*; import static org.junit.Assert.*; import java.util.ArrayList; import java.util.Collection; import java.util.List; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameters; import org.openhab.binding.modbus.ModbusBindingProvider; import org.openhab.core.library.types.OnOffType; import org.openhab.core.library.types.OpenClosedType; import org.openhab.core.types.Command; import net.wimpi.modbus.msg.ModbusRequest; import net.wimpi.modbus.msg.ReadCoilsRequest; import net.wimpi.modbus.msg.ReadInputDiscretesRequest; import net.wimpi.modbus.msg.WriteCoilRequest; import net.wimpi.modbus.procimg.DigitalIn; import net.wimpi.modbus.procimg.DigitalOut; import net.wimpi.modbus.procimg.SimpleDigitalIn; import net.wimpi.modbus.procimg.SimpleDigitalOut; @RunWith(Parameterized.class) public class WriteCoilsAndDiscreteTestCase extends TestCaseSupport { private static final int BIT_READ_COUNT = 2; private static Command[] ZERO_COMMANDS = new Command[] { OnOffType.OFF, OpenClosedType.CLOSED }; private static Command[] ONE_COMMANDS = new Command[] { OnOffType.ON, OpenClosedType.OPEN }; @SuppressWarnings("serial") public static class ExpectedFailure extends AssertionError { } /** * Create cross product of test parameters * * @param expectedValue * @param commands * @return */ private static ArrayList<Object[]> generateParameters(Object expectedValue, Command... commands) { ArrayList<Object[]> parameters = new ArrayList<>(); for (ServerType serverType : TEST_SERVERS) { for (boolean discreteInputInitialValue : new Boolean[] { true, false }) { for (boolean coilInitialValue : new Boolean[] { true, false }) { for (boolean nonZeroOffset : new Boolean[] { true, false }) { for (Command command : commands) { for (int itemIndex : new Integer[] { 0, 1 }) { for (String type : new String[] { ModbusBindingProvider.TYPE_COIL, ModbusBindingProvider.TYPE_DISCRETE }) { parameters.add(new Object[] { serverType, discreteInputInitialValue, coilInitialValue, nonZeroOffset, type, itemIndex, command, expectedValue }); } } } } } } } return parameters; } @Parameters public static Collection<Object[]> parameters() { List<Object[]> parameters = generateParameters(false, ZERO_COMMANDS); parameters.addAll(generateParameters(true, ONE_COMMANDS)); return parameters; } private boolean nonZeroOffset; private String type; private int itemIndex; private Command command; private boolean expectedValue; private boolean coilInitialValue; private boolean discreteInitialValue; private DigitalIn[] dins; private DigitalOut[] douts; /** * @param serverType type of server * @param discreteInitialValue * initial value of the discrete inputs * @param coilInitialValue * initial value of the coils * @param nonZeroOffset * whether to test non-zero start address in modbus binding * @param type * type of the slave (e.g. "holding") * @param itemIndex * index of the item that receives command * @param command * received command * @param expectedValue * expected boolean written to corresponding coil/discrete input. */ public WriteCoilsAndDiscreteTestCase(ServerType serverType, boolean discreteInitialValue, boolean coilInitialValue, boolean nonZeroOffset, String type, int itemIndex, Command command, boolean expectedValue) { this.serverType = serverType; this.discreteInitialValue = discreteInitialValue; this.coilInitialValue = coilInitialValue; this.nonZeroOffset = nonZeroOffset; this.type = type; this.itemIndex = itemIndex; this.command = command; this.expectedValue = expectedValue; } @Override @Before public void setUp() throws Exception { super.setUp(); initSpi(); } /** * Test writing of discrete inputs (i.e. digital inputs)/coils (i.e. digital * outputs), uses default valuetype * * * @throws Exception */ @Test public void testWriteDigitalsNoReads() throws Exception { // XXX /* * Test is ignored since current implementation synchronized(storage) on coil commands which throws * NullPointerException */ if (type == ModbusBindingProvider.TYPE_COIL) { return; } binding = new ModbusBinding(); int offset = (nonZeroOffset ? 1 : 0); binding.updated(addSlave(newLongPollBindingConfig(), SLAVE_NAME, type, null, offset, 2)); configureSwitchItemBinding(2, SLAVE_NAME, 0); try { binding.receiveCommand(String.format("Item%s", itemIndex + 1), command); } catch (NullPointerException e) { if (type != ModbusBindingProvider.TYPE_COIL) { fail("Expecting NullPointerException only with coil"); } return; } if (type == ModbusBindingProvider.TYPE_COIL) { String msg = "Should have raised NullPointerException with coil"; fail(msg); } verifyRequests(false); } @Test public void testWriteDigitalsAfterRead() throws Exception { binding = new ModbusBinding(); int offset = (nonZeroOffset ? 1 : 0); binding.updated(addSlave(newLongPollBindingConfig(), SLAVE_NAME, type, null, offset, BIT_READ_COUNT)); configureSwitchItemBinding(2, SLAVE_NAME, 0); // READ -- initializes register binding.execute(); binding.receiveCommand(String.format("Item%s", itemIndex + 1), command); verifyRequests(true); } private void verifyRequests(boolean readRequestExpected) throws Exception { try { ArrayList<ModbusRequest> requests = modbustRequestCaptor.getAllReturnValues(); int expectedDOIndex = nonZeroOffset ? (itemIndex + 1) : itemIndex; WriteCoilRequest writeRequest; boolean writeExpected = type == ModbusBindingProvider.TYPE_COIL; int expectedRequests = (writeExpected ? 1 : 0) + (readRequestExpected ? 1 : 0); // One connection per request int expectedConnections = serverType.equals(ServerType.UDP) ? 1 : expectedRequests; // Give the system 5 seconds to make the expected connections & requests waitForConnectionsReceived(expectedConnections); waitForRequests(expectedRequests); assertThat(requests.size(), is(equalTo(expectedRequests))); if (readRequestExpected) { if (type == ModbusBindingProvider.TYPE_DISCRETE) { assertThat(requests.get(0), is(instanceOf(ReadInputDiscretesRequest.class))); } else if (type == ModbusBindingProvider.TYPE_COIL) { assertThat(requests.get(0), is(instanceOf(ReadCoilsRequest.class))); } else { throw new RuntimeException(); } } if (writeExpected) { assertThat(requests.get(expectedRequests - 1), is(instanceOf(WriteCoilRequest.class))); writeRequest = (WriteCoilRequest) requests.get(expectedRequests - 1); assertThat(writeRequest.getCoil(), is(equalTo(expectedValue))); assertThat(writeRequest.getReference(), is(equalTo(expectedDOIndex))); } } catch (AssertionError e) { System.err.println(String.format( "Unexpected assertion error: discreteInitial=%s, coilInitial=%s, nonZeroOffset=%s, command=%s, itemIndex=%d, type=%s", discreteInitialValue, coilInitialValue, nonZeroOffset, command, itemIndex, type)); throw new AssertionError("Got unexpected assertion error", e); } System.err.println(String.format( "OK: discreteInitial=%s, coilInitial=%s, nonZeroOffset=%s, command=%s, itemIndex=%d, type=%s", discreteInitialValue, coilInitialValue, nonZeroOffset, command, itemIndex, type)); } private void initSpi() { dins = new DigitalIn[] { new SimpleDigitalIn(discreteInitialValue), new SimpleDigitalIn(discreteInitialValue), new SimpleDigitalIn(discreteInitialValue), new SimpleDigitalIn(discreteInitialValue) }; for (DigitalIn din : dins) { spi.addDigitalIn(din); } douts = new DigitalOut[] { new SimpleDigitalOut(coilInitialValue), new SimpleDigitalOut(coilInitialValue), new SimpleDigitalOut(coilInitialValue), new SimpleDigitalOut(coilInitialValue) }; for (DigitalOut dout : douts) { spi.addDigitalOut(dout); } } }