/** * 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.mockito.Mockito.*; import java.net.UnknownHostException; import java.util.Arrays; import java.util.Collection; import java.util.Dictionary; 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.Transformation.TransformationHelperWrapper; import org.openhab.core.library.items.NumberItem; import org.openhab.core.library.items.StringItem; import org.openhab.core.library.items.SwitchItem; import org.openhab.core.library.types.DecimalType; import org.openhab.core.library.types.OnOffType; import org.openhab.core.library.types.StringType; import org.openhab.core.transform.TransformationException; import org.openhab.core.transform.TransformationService; import org.openhab.model.item.binding.BindingConfigParseException; import org.osgi.framework.BundleContext; import org.osgi.service.cm.ConfigurationException; import net.wimpi.modbus.procimg.SimpleInputRegister; import net.wimpi.modbus.procimg.SimpleRegister; /** * Tests for items with extended syntax. Run only against TCP server. * */ @RunWith(Parameterized.class) public class ReadRegistersExtendedItemConfigurationTestCase extends TestCaseSupport { private ModbusGenericBindingProvider provider; private String type; @Parameters public static Collection<Object[]> parameters() { return Arrays.asList( new Object[][] { { ModbusBindingProvider.TYPE_HOLDING }, { ModbusBindingProvider.TYPE_INPUT } }); } public ReadRegistersExtendedItemConfigurationTestCase(String type) { if (!type.equals(ModbusBindingProvider.TYPE_HOLDING) && !type.equals(ModbusBindingProvider.TYPE_INPUT)) { throw new IllegalStateException("Test does not support this type"); } this.type = type; } private void addRegister(int value) { if (type.equals(ModbusBindingProvider.TYPE_HOLDING)) { spi.addRegister(new SimpleRegister(value)); } else { spi.addInputRegister(new SimpleInputRegister(value)); } } private void setRegister(int register, int newValue) { if (type.equals(ModbusBindingProvider.TYPE_HOLDING)) { spi.getRegister(register).setValue(newValue); } else { spi.setInputRegister(register, new SimpleInputRegister(newValue)); } } @Before public void initSlaveAndServer() throws Exception { addRegister(3); addRegister(5); addRegister(0); addRegister(9); addRegister(16456); addRegister(62915); binding = new ModbusBinding(); Dictionary<String, Object> config = newLongPollBindingConfig(); addSlave(config, SLAVE_NAME, type, ModbusBindingProvider.VALUE_TYPE_INT16, 0, 6); binding.updated(config); // Configure items provider = new ModbusGenericBindingProvider(); binding.setEventPublisher(eventPublisher); binding.addBindingProvider(provider); } @Test public void testNumberItemSingleReadRegisterDefaults() throws UnknownHostException, ConfigurationException, BindingConfigParseException { provider.processBindingConfiguration("test.items", new NumberItem("Item1"), String.format("<[%s:1:trigger=*]", SLAVE_NAME)); binding.execute(); waitForConnectionsReceived(1); waitForRequests(1); verify(eventPublisher).postUpdate("Item1", new DecimalType(5)); verifyNoMoreInteractions(eventPublisher); } @Test public void testNumberItemSingleReadRegisterDefaults2() throws UnknownHostException, ConfigurationException, BindingConfigParseException { provider.processBindingConfiguration("test.items", new NumberItem("Item1"), String.format("<[%s:1:trigger=*]", SLAVE_NAME)); binding.execute(); waitForConnectionsReceived(1); waitForRequests(1); verify(eventPublisher).postUpdate("Item1", new DecimalType(5)); verifyNoMoreInteractions(eventPublisher); } @Test public void testNumberItemSingleReadRegisterWithTriggerMatch() throws UnknownHostException, ConfigurationException, BindingConfigParseException { provider.processBindingConfiguration("test.items", new NumberItem("Item1"), String.format("<[%s:1:trigger=5]", SLAVE_NAME)); binding.execute(); waitForConnectionsReceived(1); waitForRequests(1); verify(eventPublisher).postUpdate("Item1", new DecimalType(5)); verifyNoMoreInteractions(eventPublisher); } @Test public void testNumberItemSingleReadRegisterWithTriggerNoMatch() throws UnknownHostException, ConfigurationException, BindingConfigParseException { // trigger 999 does not match the state 9 provider.processBindingConfiguration("test.items", new NumberItem("Item1"), String.format("<[%s:1:trigger=999]", SLAVE_NAME)); binding.execute(); waitForConnectionsReceived(1); waitForRequests(1); verifyNoMoreInteractions(eventPublisher); } @Test public void testNumberItemSingleReadRegisterWithValueTypeOverriden() throws UnknownHostException, ConfigurationException, BindingConfigParseException { provider.processBindingConfiguration("test.items", new NumberItem("Item1"), String.format("<[%s:0:trigger=*,valueType=int32]", SLAVE_NAME)); binding.execute(); waitForConnectionsReceived(1); waitForRequests(1); // 196613 in 32bit 2's complement -> 0x00030005 verify(eventPublisher).postUpdate("Item1", new DecimalType(196613)); verifyNoMoreInteractions(eventPublisher); } @Test public void testNumberItemSingleReadRegisterWithValueTypeOverriden2() throws UnknownHostException, ConfigurationException, BindingConfigParseException { provider.processBindingConfiguration("test.items", new NumberItem("Item1"), String.format("<[%s:2:trigger=*,valueType=float32]", SLAVE_NAME)); binding.execute(); waitForConnectionsReceived(1); waitForRequests(1); // constructed from 16456 and 62915 verify(eventPublisher).postUpdate("Item1", new DecimalType(3.1400001049041748046875)); verifyNoMoreInteractions(eventPublisher); } @Test public void testSwitchItemSingleReadRegisterDefaults() throws UnknownHostException, ConfigurationException, BindingConfigParseException { provider.processBindingConfiguration("test.items", new SwitchItem("Item1"), String.format("<[%s:1:trigger=*]", SLAVE_NAME)); binding.execute(); waitForConnectionsReceived(1); waitForRequests(1); verify(eventPublisher).postUpdate("Item1", OnOffType.ON); verifyNoMoreInteractions(eventPublisher); } @Test public void testSwitchItemSingleReadRegisterWithTriggerMatch() throws UnknownHostException, ConfigurationException, BindingConfigParseException { provider.processBindingConfiguration("test.items", new SwitchItem("Item1"), String.format("<[%s:1:type=STATE,trigger=ON]", SLAVE_NAME)); binding.execute(); waitForConnectionsReceived(1); waitForRequests(1); verify(eventPublisher).postUpdate("Item1", OnOffType.ON); verifyNoMoreInteractions(eventPublisher); } @Test public void testSwitchItemSingleReadRegisterWithTriggerMatch2() throws UnknownHostException, ConfigurationException, BindingConfigParseException { provider.processBindingConfiguration("test.items", new SwitchItem("Item1"), String.format("<[%s:2:trigger=OFF]", SLAVE_NAME)); binding.execute(); waitForConnectionsReceived(1); waitForRequests(1); verify(eventPublisher).postUpdate("Item1", OnOffType.OFF); verifyNoMoreInteractions(eventPublisher); } @Test public void testSwitchItemSingleReadRegisterWithTriggerNoMatch() throws UnknownHostException, ConfigurationException, BindingConfigParseException { provider.processBindingConfiguration("test.items", new SwitchItem("Item1"), String.format("<[%s:1:trigger=OFF]", SLAVE_NAME)); binding.execute(); waitForConnectionsReceived(1); waitForRequests(1); verifyNoMoreInteractions(eventPublisher); } @Test public void testNumberItemSingleReadRegisterComplexTransformation() throws UnknownHostException, ConfigurationException, BindingConfigParseException { provider.processBindingConfiguration("test.items", new NumberItem("Item1"), String.format("<[%s:1:trigger=*,transformation=MULTIPLY(5)]", SLAVE_NAME)); ModbusBindingConfig config = provider.getConfig("Item1"); // Inject transformation for (ItemIOConnection itemIOConnection : config.getReadConnections()) { itemIOConnection.getTransformation().setTransformationHelper(new TransformationHelperWrapper() { @Override public TransformationService getTransformationService(BundleContext context, String transformationServiceName) { if ("MULTIPLY".equals(transformationServiceName)) { return new TransformationService() { @Override public String transform(String multiplier, String arg) throws TransformationException { return String.valueOf(Integer.valueOf(multiplier) * Integer.valueOf(arg)); } }; } else { throw new AssertionError("unexpected transformation"); } } }); } binding.execute(); waitForConnectionsReceived(1); waitForRequests(1); verify(eventPublisher).postUpdate("Item1", new DecimalType(25)); verifyNoMoreInteractions(eventPublisher); } @Test public void testNumberItemSingleReadRegisterComplexTransformation2() throws UnknownHostException, ConfigurationException, BindingConfigParseException { provider.processBindingConfiguration("test.items", new StringItem("Item1"), String.format("<[%s:1:trigger=*,transformation=STRINGMULTIPLY(5)]", SLAVE_NAME)); ModbusBindingConfig config = provider.getConfig("Item1"); // Inject transformation for (ItemIOConnection itemIOConnection : config.getReadConnections()) { itemIOConnection.getTransformation().setTransformationHelper(new TransformationHelperWrapper() { @Override public TransformationService getTransformationService(BundleContext context, String transformationServiceName) { if ("STRINGMULTIPLY".equals(transformationServiceName)) { return new TransformationService() { @Override public String transform(String multiplier, String arg) throws TransformationException { return "foobar_" + String.valueOf(Integer.valueOf(multiplier) * Integer.valueOf(arg)); } }; } else { throw new AssertionError("unexpected transformation"); } } }); } binding.execute(); waitForConnectionsReceived(1); waitForRequests(1); verify(eventPublisher).postUpdate("Item1", new StringType("foobar_25")); verifyNoMoreInteractions(eventPublisher); // Execute again, since updates unchanged a new event should be sent reset(eventPublisher); binding.execute(); waitForConnectionsReceived(2); waitForRequests(2); verify(eventPublisher).postUpdate("Item1", new StringType("foobar_25")); verifyNoMoreInteractions(eventPublisher); } @Test public void testNumberItemSingleReadRegisterComplexTransformation2DefaultTrigger() throws UnknownHostException, ConfigurationException, BindingConfigParseException { provider.processBindingConfiguration("test.items", new StringItem("Item1"), String.format("<[%s:1:transformation=STRINGMULTIPLY(5)]", SLAVE_NAME)); ModbusBindingConfig config = provider.getConfig("Item1"); // Inject transformation for (ItemIOConnection itemIOConnection : config.getReadConnections()) { itemIOConnection.getTransformation().setTransformationHelper(new TransformationHelperWrapper() { @Override public TransformationService getTransformationService(BundleContext context, String transformationServiceName) { if ("STRINGMULTIPLY".equals(transformationServiceName)) { return new TransformationService() { @Override public String transform(String multiplier, String arg) throws TransformationException { return "foobar_" + String.valueOf(Integer.valueOf(multiplier) * Integer.valueOf(arg)); } }; } else { throw new AssertionError("unexpected transformation"); } } }); } binding.execute(); waitForConnectionsReceived(1); waitForRequests(1); verify(eventPublisher).postUpdate("Item1", new StringType("foobar_25")); verifyNoMoreInteractions(eventPublisher); // Execute again, since does not update unchanged (slave default) *NO* new event should be sent reset(eventPublisher); binding.execute(); waitForConnectionsReceived(2); waitForRequests(2); verifyNoMoreInteractions(eventPublisher); } @Test public void testNumberItemReadTwoRegistersComplexTransformation3() throws UnknownHostException, ConfigurationException, BindingConfigParseException { provider.processBindingConfiguration("test.items", new StringItem("Item1"), String.format( "<[%1$s:1:trigger=CHANGED,transformation=STRINGMULTIPLY(5)],<[%1$s:2:trigger=CHANGED,transformation=PLUS(7)]", SLAVE_NAME)); ModbusBindingConfig config = provider.getConfig("Item1"); // Inject transformation for (ItemIOConnection itemIOConnection : config.getReadConnections()) { itemIOConnection.getTransformation().setTransformationHelper(new TransformationHelperWrapper() { @Override public TransformationService getTransformationService(BundleContext context, String transformationServiceName) { if ("STRINGMULTIPLY".equals(transformationServiceName)) { return new TransformationService() { @Override public String transform(String multiplier, String arg) throws TransformationException { return "foobar_" + String.valueOf(Integer.valueOf(multiplier) * Integer.valueOf(arg)); } }; } else if ("PLUS".equals(transformationServiceName)) { return new TransformationService() { @Override public String transform(String offset, String arg) throws TransformationException { return "foobar_" + String.valueOf(Integer.valueOf(offset) + Integer.valueOf(arg)); } }; } else { throw new AssertionError("unexpected transformation"); } } }); } binding.execute(); waitForConnectionsReceived(1); waitForRequests(1); // two events on the first time(the changed-on-poll check is done for each connection separately) verify(eventPublisher).postUpdate("Item1", new StringType("foobar_25")); verify(eventPublisher).postUpdate("Item1", new StringType("foobar_7")); verifyNoMoreInteractions(eventPublisher); reset(eventPublisher); binding.execute(); waitForConnectionsReceived(2); waitForRequests(2); // no events on the second time, nothing has changed verifyNoMoreInteractions(eventPublisher); // modify server data setRegister(2, 5); reset(eventPublisher); binding.execute(); waitForConnectionsReceived(3); waitForRequests(3); // only the PLUS connection was activated verify(eventPublisher).postUpdate("Item1", new StringType("foobar_12")); verifyNoMoreInteractions(eventPublisher); } }