/** * 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 java.util.ArrayList; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import org.apache.commons.pool2.KeyedObjectPool; import org.junit.Assert; 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.binding.modbus.internal.pooling.ModbusSlaveEndpoint; import org.openhab.binding.modbus.internal.pooling.ModbusTCPSlaveEndpoint; import org.openhab.core.library.types.DecimalType; import org.openhab.core.types.Command; import org.openhab.core.types.State; import org.openhab.model.item.binding.BindingConfigParseException; import net.wimpi.modbus.msg.ModbusRequest; import net.wimpi.modbus.msg.ReadMultipleRegistersRequest; import net.wimpi.modbus.msg.WriteSingleRegisterRequest; import net.wimpi.modbus.net.ModbusSlaveConnection; import net.wimpi.modbus.procimg.SimpleInputRegister; import net.wimpi.modbus.procimg.SimpleRegister; @RunWith(Parameterized.class) public class SimultaneousReadWriteTestCase extends TestCaseSupport { private static final int READ_COUNT = 4; @Parameters public static List<Object[]> parameters() { List<Object[]> parameters = new ArrayList<>(); for (ServerType serverType : TestCaseSupport.TEST_SERVERS) { parameters.add(new Object[] { serverType, new short[] { 5, 5, 5, 5, 5 }, ModbusBindingProvider.TYPE_HOLDING, ModbusBindingProvider.VALUE_TYPE_INT16, new DecimalType(5.0) }); } return parameters; } private String valueType; private String type; private Command command; private short[] registerInitialValues; private State itemInitialState; /* */ public SimultaneousReadWriteTestCase(ServerType serverType, short[] registerInitialValues, String type, String valueType, Command command) { this.serverType = serverType; this.registerInitialValues = registerInitialValues; this.type = type; this.valueType = valueType; this.command = command; // Server is a bit slower to respond than norrmally, so we certainly get clashes with read/write this.artificialServerWait = 500; } @Override @Before public void setUp() throws Exception { super.setUp(); initSpi(); } private void configureItems(String slave) throws BindingConfigParseException { configureNumberItemBinding(2, slave, 0, slave, itemInitialState); } private static class UpdateThread extends Thread { private ModbusBinding binding; public UpdateThread(ModbusBinding binding) { this.binding = binding; } @Override public void run() { binding.execute(); } } private static class WriteCommandThread extends Thread { private ModbusBinding binding; private String slave; private Command command; public WriteCommandThread(ModbusBinding binding, String slave, Command command) { this.binding = binding; this.slave = slave; this.command = command; } @Override public void run() { binding.receiveCommand(String.format("%sItem%s", slave, 1), command); } } /** * Testing how binding handles simultaneous read and writes coming in. * * Even though the server in this test is able to handle at most one client at a time the binding * queues requests. * * Note higher artificialServerWait in constructor * * @throws Exception */ @Test public void testSimultaneousReadWrite() throws Exception { binding = new ModbusBinding(); binding.updated(addSlave(addSlave(newLongPollBindingConfig(), SLAVE_NAME, type, null, 0, READ_COUNT), SLAVE2_NAME, type, null, 0, READ_COUNT)); configureItems(SLAVE_NAME); configureItems(SLAVE2_NAME); /* * - both slaves read twice -> 4 read requests * - followed by write (slave1) -> 1 write request * - both slaves read once -> 2 read requests. * - Finally three writes (slave2) -> 3 write requets */ int expectedRequests = 10; ExecutorService pool = Executors.newFixedThreadPool(expectedRequests); binding.execute(); pool.execute(new UpdateThread(binding)); pool.execute(new WriteCommandThread(binding, SLAVE_NAME, command)); pool.execute(new UpdateThread(binding)); pool.execute(new WriteCommandThread(binding, SLAVE2_NAME, command)); pool.execute(new WriteCommandThread(binding, SLAVE2_NAME, command)); pool.execute(new WriteCommandThread(binding, SLAVE2_NAME, command)); pool.shutdown(); pool.awaitTermination(artificialServerWait * 7 + 5000, TimeUnit.MILLISECONDS); waitForRequests(expectedRequests); ArrayList<ModbusRequest> values = modbustRequestCaptor.getAllReturnValues(); System.err.println(values); int readCount = 0; int writeCount = 0; for (ModbusRequest request : values) { if (request instanceof ReadMultipleRegistersRequest) { readCount++; } else if (request instanceof WriteSingleRegisterRequest) { writeCount++; } } Assert.assertEquals(6, readCount); Assert.assertEquals(4, writeCount); } @Test public void testPoolBlocks() throws Exception { final KeyedObjectPool<ModbusSlaveEndpoint, ModbusSlaveConnection> pool = ModbusBinding .getReconstructedConnectionPoolForTesting(); final ModbusTCPSlaveEndpoint endpoint = new ModbusTCPSlaveEndpoint(localAddress().getHostAddress(), this.tcpModbusPort); ModbusSlaveConnection borrowObject = pool.borrowObject(endpoint); Thread thread = new Thread() { @Override public void run() { try { ModbusSlaveConnection borrowObject2 = pool.borrowObject(endpoint); pool.returnObject(endpoint, borrowObject2); } catch (Exception e) { e.printStackTrace(System.err); } } }; thread.start(); thread.join(500); if (!thread.isAlive()) { throw new AssertionError("Thread should still be alive -- blocking since no objects"); } else { thread.interrupt(); } pool.returnObject(endpoint, borrowObject); // Now that object has been returned, borrowing should work again ModbusSlaveConnection borrowObject2 = pool.borrowObject(endpoint); pool.returnObject(endpoint, borrowObject2); } private void initSpi() { int registerCount = registerInitialValues.length; for (int i = 0; i < registerCount; i++) { if (ModbusBindingProvider.TYPE_HOLDING.equals(type)) { spi.addRegister(new SimpleRegister(registerInitialValues[i])); } else if (ModbusBindingProvider.TYPE_INPUT.equals(type)) { spi.addInputRegister(new SimpleInputRegister(registerInitialValues[i])); } } } }