/** * 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.assertThat; import static org.mockito.Mockito.*; import java.util.Dictionary; import org.junit.Before; import org.junit.Test; import org.openhab.binding.modbus.ModbusBindingProvider; import org.openhab.binding.modbus.internal.Transformation.TransformationHelperWrapper; import org.openhab.core.library.items.DimmerItem; import org.openhab.core.library.items.NumberItem; import org.openhab.core.library.items.RollershutterItem; 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.IncreaseDecreaseType; import org.openhab.core.library.types.OnOffType; import org.openhab.core.library.types.PercentType; import org.openhab.core.library.types.StopMoveType; import org.openhab.core.library.types.StringType; import org.openhab.core.library.types.UpDownType; 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 net.wimpi.modbus.procimg.SimpleRegister; /** * Tests for items with extended syntax. Run only against TCP server. * */ public class WriteRegisterExtendedItemConfigurationTestCase extends TestCaseSupport { private ModbusGenericBindingProvider provider; @Before public void initSlaveAndServer() throws Exception { spi.addRegister(new SimpleRegister(9)); spi.addRegister(new SimpleRegister(10)); // binding = new ModbusBinding(); Dictionary<String, Object> config = newLongPollBindingConfig(); addSlave(config, SLAVE_NAME, ModbusBindingProvider.TYPE_HOLDING, ModbusBindingProvider.VALUE_TYPE_INT16, 0, 2); binding.updated(config); // Configure items provider = new ModbusGenericBindingProvider(); binding.setEventPublisher(eventPublisher); binding.addBindingProvider(provider); } @Test public void testRegisterWriteRollershutterItemManyConnections() throws BindingConfigParseException { // Inspired by https://github.com/openhab/openhab/pull/4654 provider.processBindingConfiguration("test.items", new RollershutterItem("Item1"), String.format( ">[%1$s:0:trigger=UP,transformation=1],>[%1$s:0:trigger=DOWN,transformation=-1]" + ",>[%1$s:1:trigger=MOVE,transformation=1],>[%1$s:1:trigger=STOP,transformation=0]", SLAVE_NAME)); binding.execute(); verifyNoMoreInteractions(eventPublisher); // write-only item, no event sent binding.receiveCommand("Item1", UpDownType.UP); assertThat(spi.getRegister(0).getValue(), is(equalTo(1))); assertThat(spi.getRegister(1).getValue(), is(equalTo(10))); } @Test public void testRegisterWriteIncreaseWithoutRead() throws BindingConfigParseException { // Inspired by https://github.com/openhab/openhab/pull/4654 provider.processBindingConfiguration("test.items", new DimmerItem("Item1"), String.format(">[%1$s:0],<[%1$s:0]", SLAVE_NAME)); // binding.execute(); no read verifyNoMoreInteractions(eventPublisher); binding.receiveCommand("Item1", IncreaseDecreaseType.INCREASE); // Binding cannot execute the command since there is no polled value // -> no change in registers assertThat(spi.getRegister(0).getValue(), is(equalTo(9))); assertThat(spi.getRegister(1).getValue(), is(equalTo(10))); } @Test public void testRegisterWriteIncreaseWithRead() throws BindingConfigParseException { // Read index 1 (value=10) and (INCREASE command) increments it by one -> 11. Written to index 0 provider.processBindingConfiguration("test.items", new DimmerItem("Item1"), String.format(">[%1$s:0],<[%1$s:1]", SLAVE_NAME)); binding.execute(); // read verify(eventPublisher).postUpdate("Item1", new DecimalType(10)); verifyNoMoreInteractions(eventPublisher); binding.receiveCommand("Item1", IncreaseDecreaseType.INCREASE); assertThat(spi.getRegister(0).getValue(), is(equalTo(11))); assertThat(spi.getRegister(1).getValue(), is(equalTo(10))); } @Test public void testRegisterWriteIncreaseWithRead2() throws BindingConfigParseException { // Read index 1 (value=10) and index 0 (value=9). // INCREASE command increments last read value (9) by one -> 10. Written to index 0 provider.processBindingConfiguration("test.items", new DimmerItem("Item1"), String.format(">[%1$s:0],<[%1$s:1],<[%1$s:0]", SLAVE_NAME)); binding.execute(); // read verify(eventPublisher).postUpdate("Item1", new DecimalType(10)); verify(eventPublisher).postUpdate("Item1", new DecimalType(9)); verifyNoMoreInteractions(eventPublisher); binding.receiveCommand("Item1", IncreaseDecreaseType.INCREASE); // Binding cannot execute the command since there is no polled value // -> no change in registers assertThat(spi.getRegister(0).getValue(), is(equalTo(10))); assertThat(spi.getRegister(1).getValue(), is(equalTo(10))); } @Test public void testRegisterWriteIncreaseWithRead3() throws BindingConfigParseException { // same as testRegisterWriteIncreaseWithRead2 but order of read connections is flipped provider.processBindingConfiguration("test.items", new DimmerItem("Item1"), String.format(">[%1$s:0],<[%1$s:0],<[%1$s:1]", SLAVE_NAME)); binding.execute(); // read verify(eventPublisher).postUpdate("Item1", new DecimalType(10)); verify(eventPublisher).postUpdate("Item1", new DecimalType(9)); verifyNoMoreInteractions(eventPublisher); binding.receiveCommand("Item1", IncreaseDecreaseType.INCREASE); // Binding cannot execute the command since there is no polled value // -> no change in registers assertThat(spi.getRegister(0).getValue(), is(equalTo(11))); assertThat(spi.getRegister(1).getValue(), is(equalTo(10))); } @Test public void testRegisterWriteIncreaseWithTransformation() throws BindingConfigParseException { provider.processBindingConfiguration("test.items", new DimmerItem("Item1"), String.format(">[%1$s:0:transformation=3],<[%1$s:0]", SLAVE_NAME)); verifyNoMoreInteractions(eventPublisher); binding.receiveCommand("Item1", IncreaseDecreaseType.INCREASE); // Binding will be able to write the value even without previously polled value since the transformation // converts INCREASE to constant 3 assertThat(spi.getRegister(0).getValue(), is(equalTo(3))); assertThat(spi.getRegister(1).getValue(), is(equalTo(10))); } @Test public void testRegisterWriteRollershutterItemManyConnections2() throws BindingConfigParseException { // Inspired by https://github.com/openhab/openhab/pull/4654 provider.processBindingConfiguration("test.items", new RollershutterItem("Item1"), String.format( ">[%1$s:0:trigger=UP,transformation=1],>[%1$s:0:trigger=DOWN,transformation=-1]" + ",>[%1$s:1:trigger=MOVE,transformation=1],>[%1$s:1:trigger=STOP,transformation=0]", SLAVE_NAME)); binding.execute(); verifyNoMoreInteractions(eventPublisher); // write-only item, no event sent binding.receiveCommand("Item1", UpDownType.DOWN); // 65535 is same as -1, the SimpleRegister.getValue just returns the unsigned 16bit representation of the // register assertThat(spi.getRegister(0).getValue(), is(equalTo(65535))); assertThat(spi.getRegister(1).getValue(), is(equalTo(10))); } @Test public void testRegisterWriteRollershutterItemManyConnections3() throws BindingConfigParseException { // Inspired by https://github.com/openhab/openhab/pull/4654 provider.processBindingConfiguration("test.items", new RollershutterItem("Item1"), String.format( ">[%1$s:0:trigger=UP,transformation=1],>[%1$s:0:trigger=DOWN,transformation=-1]" + ",>[%1$s:1:trigger=MOVE,transformation=1],>[%1$s:1:trigger=STOP,transformation=0]", SLAVE_NAME)); binding.execute(); verifyNoMoreInteractions(eventPublisher); // write-only item, no event sent binding.receiveCommand("Item1", StopMoveType.MOVE); assertThat(spi.getRegister(0).getValue(), is(equalTo(9))); assertThat(spi.getRegister(1).getValue(), is(equalTo(1))); } @Test public void testRegisterWriteRollershutterItemManyConnections4() throws BindingConfigParseException { // Inspired by https://github.com/openhab/openhab/pull/4654 provider.processBindingConfiguration("test.items", new RollershutterItem("Item1"), String.format( ">[%1$s:0:trigger=UP,transformation=1],>[%1$s:0:trigger=DOWN,transformation=-1]" + ",>[%1$s:1:trigger=MOVE,transformation=1],>[%1$s:1:trigger=STOP,transformation=0]", SLAVE_NAME)); binding.execute(); verifyNoMoreInteractions(eventPublisher); // write-only item, no event sent binding.receiveCommand("Item1", StopMoveType.STOP); assertThat(spi.getRegister(0).getValue(), is(equalTo(9))); assertThat(spi.getRegister(1).getValue(), is(equalTo(0))); } @Test public void testRegisterWriteRollershutterWriteFiltered() throws BindingConfigParseException { provider.processBindingConfiguration("test.items", new RollershutterItem("Item1"), String.format(">[%1$s:0:trigger=UP,transformation=1],>[%1$s:0:trigger=DOWN,transformation=-1]" + ",>[%1$s:1:trigger=MOVE,transformation=1]", SLAVE_NAME)); binding.execute(); verifyNoMoreInteractions(eventPublisher); // write-only item, no event sent binding.receiveCommand("Item1", StopMoveType.STOP); // Stop was not processed assertThat(spi.getRegister(0).getValue(), is(equalTo(9))); assertThat(spi.getRegister(1).getValue(), is(equalTo(10))); } @Test public void testRegisterWriteSwitchItemNonNumbericTransformationAndTwoRegistersManyConnections4() throws BindingConfigParseException { provider.processBindingConfiguration("test.items", new SwitchItem("Item1"), String.format( ">[%1$s:0:trigger=OFF,transformation=ON],>[%1$s:1:trigger=OFF,transformation=OFF]", SLAVE_NAME)); binding.execute(); verifyNoMoreInteractions(eventPublisher); // write-only item, no event sent binding.receiveCommand("Item1", OnOffType.OFF); // two registers were changed at the same time assertThat(spi.getRegister(0).getValue(), is(equalTo(1))); assertThat(spi.getRegister(1).getValue(), is(equalTo(0))); } @Test public void testRegisterWritePercentTypeWithTransformation() throws BindingConfigParseException { provider.processBindingConfiguration("test.items", new NumberItem("Item1"), String.format(">[%1$s:0]", SLAVE_NAME)); binding.execute(); verifyNoMoreInteractions(eventPublisher); // write-only item, no event sent binding.receiveCommand("Item1", new PercentType("3.4")); // percent rounded down assertThat(spi.getRegister(0).getValue(), is(equalTo(3))); assertThat(spi.getRegister(1).getValue(), is(equalTo(10))); } @Test public void testRegisterWriteNumberItemComplexTransformation() throws BindingConfigParseException { provider.processBindingConfiguration("test.items", new NumberItem("Item1"), String.format(">[%1$s:0:trigger=*,transformation=MULTIPLY(3)]", SLAVE_NAME)); ModbusBindingConfig config = provider.getConfig("Item1"); // Inject transformation for (ItemIOConnection itemIOConnection : config.getWriteConnections()) { 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(); verifyNoMoreInteractions(eventPublisher); // write-only item, no event sent binding.receiveCommand("Item1", new DecimalType("4")); // two registers were changed at the same time assertThat(spi.getRegister(0).getValue(), is(equalTo(12))); assertThat(spi.getRegister(1).getValue(), is(equalTo(10))); } @Test public void testRegisterWriteNumberItemComplexTransformation2() throws BindingConfigParseException { provider.processBindingConfiguration("test.items", new StringItem("Item1"), String.format(">[%1$s:0:trigger=*,transformation=LEN()]", SLAVE_NAME)); ModbusBindingConfig config = provider.getConfig("Item1"); // Inject transformation for (ItemIOConnection itemIOConnection : config.getWriteConnections()) { itemIOConnection.getTransformation().setTransformationHelper(new TransformationHelperWrapper() { @Override public TransformationService getTransformationService(BundleContext context, String transformationServiceName) { if ("LEN".equals(transformationServiceName)) { return new TransformationService() { @Override public String transform(String multiplier, String arg) throws TransformationException { return String.valueOf(arg.length()); } }; } else { throw new AssertionError("unexpected transformation"); } } }); } binding.execute(); verifyNoMoreInteractions(eventPublisher); // write-only item, no event sent binding.receiveCommand("Item1", new StringType("foob")); // two registers were changed at the same time assertThat(spi.getRegister(0).getValue(), is(equalTo(4))); assertThat(spi.getRegister(1).getValue(), is(equalTo(10))); } @Test public void testRegisterWriteNumberItemComplexTransformationTwoOutputs() throws BindingConfigParseException { provider.processBindingConfiguration("test.items", new StringItem("Item1"), String.format(">[%1$s:0:transformation=CHAR(0)],>[%1$s:1:transformation=CHAR(1)]", SLAVE_NAME)); ModbusBindingConfig config = provider.getConfig("Item1"); // Inject transformation for (ItemIOConnection itemIOConnection : config.getWriteConnections()) { itemIOConnection.getTransformation().setTransformationHelper(new TransformationHelperWrapper() { @Override public TransformationService getTransformationService(BundleContext context, String transformationServiceName) { if ("CHAR".equals(transformationServiceName)) { return new TransformationService() { @Override public String transform(String index, String arg) throws TransformationException { int charIdx = arg.charAt(Integer.valueOf(index)); return String.valueOf(charIdx); } }; } else { throw new AssertionError("unexpected transformation"); } } }); } binding.execute(); verifyNoMoreInteractions(eventPublisher); // write-only item, no event sent binding.receiveCommand("Item1", new StringType("foob")); // two registers were changed at the same time assertThat(spi.getRegister(0).getValue(), is(equalTo(102))); // 102 = f assertThat(spi.getRegister(1).getValue(), is(equalTo(111))); // 111 = o } }