/**
* 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 java.util.Arrays;
import java.util.List;
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.internal.ItemIOConnection.IOType;
import org.openhab.core.items.Item;
import org.openhab.core.library.items.StringItem;
import org.openhab.core.library.types.StringType;
import org.openhab.model.item.binding.BindingConfigParseException;
@RunWith(Parameterized.class)
public class ModbusBindingConfigTest {
private static StringItem stringItemWithState(String name, StringType state) {
StringItem stringItem = new StringItem(name);
stringItem.setState(state);
return stringItem;
}
@Parameters
public static List<Object[]> parameters() {
List<Object[]> parameters = Arrays
.<Object[]> asList(
// Simple case, one index
new Object[] { new StringItem("item1"), "slave1:99",
new ItemIOConnection[] { new ItemIOConnection("slave1", 99, IOType.STATE) },
new ItemIOConnection[] { new ItemIOConnection("slave1", 99, IOType.COMMAND) }, null },
// Simple case, one index, initial state
new Object[] { stringItemWithState("item1", new StringType("foobar")), "slave1:99",
new ItemIOConnection[] { new ItemIOConnection("slave1", 99, IOType.STATE) },
new ItemIOConnection[] { new ItemIOConnection("slave1", 99, IOType.COMMAND) }, null },
// Simple, read and write index different
new Object[] { new StringItem("item1"), "slave1:<100:>99",
new ItemIOConnection[] { new ItemIOConnection("slave1", 100, IOType.STATE) },
new ItemIOConnection[] { new ItemIOConnection("slave1", 99, IOType.COMMAND) }, null },
// Simple, read and write index different, order different
new Object[] { new StringItem("item1"), "slave1:>99:<100",
new ItemIOConnection[] { new ItemIOConnection("slave1", 100, IOType.STATE) },
new ItemIOConnection[] { new ItemIOConnection("slave1", 99, IOType.COMMAND) }, null },
// invalid, index missing
new Object[] { new StringItem("item1"), "slave1", null, null,
new BindingConfigParseException(
"Invalid number of registers in item 'item1' configuration") },
// invalid, two write index
new Object[] { new StringItem("item1"), "slave1:>99:>100", null, null,
new BindingConfigParseException("Register references should be either :X or :<X:>Y") },
// invalid, two read index
new Object[] { new StringItem("item1"), "slave1:<99:<100", null, null,
new BindingConfigParseException("Register references should be either :X or :<X:>Y") },
// invalid, one read index
new Object[] { new StringItem("item1"), "slave1:<99", null, null,
new BindingConfigParseException(
"Item 'item1' config ('slave1:<99') parsing failed: java.lang.NumberFormatException: For input "
+ "string: \"<99\"") },
// extended, single read using keywords
new Object[] { new StringItem("item2"),
"<[slave1:99:trigger=*,transformation=TRANSFORMATION,valueType=int32]",
new ItemIOConnection[] { new ItemIOConnection("slave1", 99, IOType.STATE, "*",
new Transformation("TRANSFORMATION", null, null), "int32") },
new ItemIOConnection[0], null },
// extended, single read using keywords, some parameters omitted, and order different
new Object[] { new StringItem("item2"), "<[slave1:99:transformation=TRANSFORMATION]",
new ItemIOConnection[] { new ItemIOConnection("slave1", 99, IOType.STATE, "default",
new Transformation("TRANSFORMATION", null, null), "default") },
new ItemIOConnection[0], null },
// extended, single read, default transformation
new Object[] { new StringItem("item2"),
"<[slave1:99:trigger=*,transformation=default,valueType=int32]",
new ItemIOConnection[] { new ItemIOConnection("slave1", 99, IOType.STATE, "*",
Transformation.IDENTITY_TRANSFORMATION, "int32") },
new ItemIOConnection[0], null },
// extended, single read, default transformation, case insensitive type
new Object[] { new StringItem("item2"),
"<[slave1:99:trigger=*,transformation=default,valueType=int32]",
new ItemIOConnection[] { new ItemIOConnection("slave1", 99, IOType.STATE, "*",
Transformation.IDENTITY_TRANSFORMATION, "int32") },
new ItemIOConnection[0], null },
// extended, single read, valid transformation
new Object[] { new StringItem("item2"),
"<[slave1:99:trigger=*,transformation=JS(getValue.js),valueType=int32]",
new ItemIOConnection[] { new ItemIOConnection("slave1", 99, IOType.STATE, "*",
new Transformation("", "JS", "getValue.js"), "int32") },
new ItemIOConnection[0], null },
// extended, two reads, valid transformations
new Object[] { new StringItem("item2"),
"<[slave1:99:trigger=*,transformation=JS(getValue.js),valueType=int32],<[slave2:98:trigger=*,transformation=FUN(getValue2.js),valueType=uint16]",
new ItemIOConnection[] {
new ItemIOConnection("slave1", 99, IOType.STATE, "*",
new Transformation("", "JS", "getValue.js"), "int32"),
new ItemIOConnection("slave2", 98, IOType.STATE, "*",
new Transformation("", "FUN", "getValue2.js"), "uint16") },
new ItemIOConnection[0], null },
// extended, two reads, valid transformations; whitespace around comma
new Object[] { new StringItem("item2"),
"<[slave1:99:trigger=*,transformation=JS(getValue.js),valueType=int32]\t ,"
+ "<[slave2:98:trigger=*,transformation=\"REGEX(,.*,)\",valueType=uint16]",
new ItemIOConnection[] {
new ItemIOConnection("slave1", 99, IOType.STATE, "*",
new Transformation("", "JS", "getValue.js"), "int32"),
new ItemIOConnection("slave2", 98, IOType.STATE, "*",
new Transformation("", "REGEX", ",.*,"), "uint16") },
new ItemIOConnection[0], null },
// extended, three reads, valid transformations; whitespace around comma
new Object[] { new StringItem("item2"),
"<[slave1:99:trigger=*,transformation=JS(getValue.js),valueType=int32]\t ,"
+ "<[slave2:98:trigger=*,transformation=\"REGEX(,.*,)\",valueType=uint16],"
+ "<[slave3:97:trigger=*,transformation=\"REGEX(,.*,)\",valueType=float32]",
new ItemIOConnection[] {
new ItemIOConnection("slave1", 99, IOType.STATE, "*",
new Transformation("", "JS", "getValue.js"), "int32"),
new ItemIOConnection("slave2", 98, IOType.STATE, "*",
new Transformation("", "REGEX", ",.*,"), "uint16"),
new ItemIOConnection("slave3", 97, IOType.STATE, "*",
new Transformation("", "REGEX", ",.*,"), "float32") },
new ItemIOConnection[0], null },
// extended, two reads, valid transformations; comma in the beginning,tricky transform
new Object[] { new StringItem("item2"),
",<[slave1:99:trigger=*,transformation=JS(getValue.js),valueType=int32]\t ,"
+ "<[slave2:98:trigger=*,transformation=\"REGEX(,.*[],(.*))\",valueType=uint16]",
new ItemIOConnection[] {
new ItemIOConnection("slave1", 99, IOType.STATE, "*",
new Transformation("", "JS", "getValue.js"), "int32"),
new ItemIOConnection("slave2", 98, IOType.STATE, "*",
new Transformation("", "REGEX", ",.*[],(.*)"), "uint16") },
new ItemIOConnection[0], null },
// extended, two reads, valid transformations; comma in the beginning, transform with
// java-quoted
// characters
new Object[] { new StringItem("item2"), // FIXME
",<[slave1:99:trigger=*,transformation=JS(getValue.js),valueType=int32]\t , <[slave2:98:trigger=*,transformation=\"REGEX(\\\",.*[:]),)\",valueType=uint16]",
new ItemIOConnection[] {
new ItemIOConnection("slave1", 99, IOType.STATE, "*",
new Transformation("", "JS", "getValue.js"), "int32"),
new ItemIOConnection("slave2", 98, IOType.STATE, "*",
new Transformation("", "REGEX", "\",.*[:]),"), "uint16") },
new ItemIOConnection[0], null },
// extended, two reads, valid transformations; comma in the beginning, transform quoted but
// without
// proper java quoting. Parsing will fail
new Object[] { new StringItem("item2"),
"<[slave1:99:trigger=*,transformation=JS(getValue.js),valueType=int32]\t , <[slave2:98:trigger=*,transformation=\"REGEX(\",.*[:]),)\",valueType=uint16]",
null, null,
new BindingConfigParseException(
"Parsing of item 'item2' configuration '\t , <[slave2:98:trigger=*,transformation=\"REGEX(\",.*[:]' (as part of the whole config '<[slave1:99:trigger=*,transformation=JS(getValue.js),valueType=int32]\t , <[slave2:98:trigger=*,transformation=\"REGEX(\",.*[:]),)\",valueType=uint16]') failed: org.openhab.model.item.binding.BindingConfigParseException Invalid token '.*[:', expecting key=value") },
// one write only, valid transformations; space in the beginning, transform quoted
new Object[] { new StringItem("item2"),
" >[slave3:97:trigger=ThisIsTrigger,transformation=\"FOOBAR(=\\\",.*[:]),)\",valueType=float32]",
new ItemIOConnection[] {},
new ItemIOConnection[] { new ItemIOConnection("slave3", 97, IOType.COMMAND,
"ThisIsTrigger", new Transformation("", "FOOBAR", "=\",.*[:]),"), "float32") },
null },
// two writes, valid transformations; transform quoted
new Object[] { new StringItem("item2"),
">[slave1:99:trigger=*,transformation=JS(getValue.js),valueType=int32]\t , >[slave2:98:trigger=*,transformation=\"REGEX(\\\",.=*[:]),)\",valueType=uint16]",
new ItemIOConnection[0],
new ItemIOConnection[] {
new ItemIOConnection("slave1", 99, IOType.COMMAND, "*",
new Transformation("", "JS", "getValue.js"), "int32"),
new ItemIOConnection("slave2", 98, IOType.COMMAND, "*",
new Transformation("", "REGEX", "\",.=*[:]),"), "uint16") },
null },
// invalid valuetype
new Object[] { new StringItem("item2"), "<[slave1:99:trigger=*,valueType=invalidValueType]",
null, null,
new BindingConfigParseException(
"Parsing of item 'item2' configuration '<[slave1:99:trigger=*,valueType=invalidValueType]' "
+ "(as part of the whole config '<[slave1:99:trigger=*,valueType=invalidValueType]') "
+ "failed: org.openhab.model.item.binding.BindingConfigParseException valuetype "
+ "'invalidValueType' does not match expected: "
+ "'bit, int8, uint8, int16, uint16, int32, uint32, float32, int32_swap, "
+ "uint32_swap, float32_swap or 'default'") },
new Object[] { new StringItem("item2"), "<[slave1:99:foobarKey=*]", null, null,
new BindingConfigParseException(
"Parsing of item 'item2' configuration '<[slave1:99:foobarKey=*]' (as part of the whole "
+ "config '<[slave1:99:foobarKey=*]') failed: "
+ "org.openhab.model.item.binding.BindingConfigParseException Unexpected token "
+ "'foobarKey=*, expecting key to be one of: type, trigger, transformation, valueType") },
// all defaults
new Object[] { new StringItem("item2"), "<[slave1:101]",
new ItemIOConnection[] { new ItemIOConnection("slave1", 101, IOType.STATE) },
new ItemIOConnection[0], null },
// all defaults, ending with colon
new Object[] { new StringItem("item2"), "<[slave1:101:]",
new ItemIOConnection[] { new ItemIOConnection("slave1", 101, IOType.STATE) },
new ItemIOConnection[0], null }
);
return parameters;
}
private Item item;
private String configString;
private ItemIOConnection[] expectedReadConnections;
private ItemIOConnection[] expectedWriteConnections;
private Exception expectedError;
public ModbusBindingConfigTest(Item item, String configString, ItemIOConnection[] expectedReadConnections,
ItemIOConnection[] expectedWriteConnections, Exception expectedError) {
this.item = item;
this.configString = configString;
this.expectedReadConnections = expectedReadConnections;
this.expectedWriteConnections = expectedWriteConnections;
this.expectedError = expectedError;
}
@Test
public void testParsing() throws BindingConfigParseException {
ModbusBindingConfig config;
try {
config = new ModbusBindingConfig(item, configString);
} catch (Exception e) {
if (expectedError != null) {
assertThat(e.getClass(), is(equalTo(expectedError.getClass())));
assertThat(e.getMessage(), is(equalTo(expectedError.getMessage())));
return;
} else {
// unexpected error
throw e;
}
}
assertThat(config.getItemClass(), is(equalTo(StringItem.class)));
assertThat(config.getItemName(), is(equalTo(item.getName())));
assertThat(config.getWriteConnections().toArray(), is(equalTo(expectedWriteConnections)));
assertThat(config.getReadConnections().toArray(), is(equalTo(expectedReadConnections)));
// Previously polled state should be initialized to null such that new updates are "changes"
for (ItemIOConnection connection : config.getWriteConnections()) {
assertThat(connection.getPreviouslyPolledState(), is(equalTo(null)));
}
for (ItemIOConnection connection : config.getReadConnections()) {
assertThat(connection.getPreviouslyPolledState(), is(equalTo(null)));
}
}
}