/**
* 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.concurrent.atomic.AtomicLong;
import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.apache.commons.lang.builder.StandardToStringStyle;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.openhab.core.types.Command;
import org.openhab.core.types.State;
/**
* ItemIOConnection defines the translation of data from modbus to openhab, and vice versa.
*
*
* @author Sami Salonen
* @since 1.10.0
*
*/
public class ItemIOConnection {
private static StandardToStringStyle toStringStyle = new StandardToStringStyle();
public static final String POLL_STATE_CHANGE_TRIGGER = "CHANGED";
public static final String TRIGGER_DEFAULT = "default";
public static final String VALUETYPE_DEFAULT = "default";
static {
toStringStyle.setUseShortClassName(true);
}
public static enum IOType {
STATE,
COMMAND
};
/**
* Name of the ModbusSlave instance to read/write data
*/
private String slaveName;
/**
* Index to read/write, relative to slave's start
*/
private int index;
/**
* On write (outbound) IO connections, type determines whether the binding should listen for state updates or
* commands
* On read (inbound) IO connections, type determines whether the binding should emit state updates or commands
*
* Currently (2017/02) the binding does not implement this extended support, however. We always use STATE type for
* read connections, and COMMAND type for write connections.
*/
private ItemIOConnection.IOType type;
/**
* On write IO connections: string representation of the non-transformed command that are accepted by this IO
* connection, and thus
* should be written to modbus slave.
*
* On Read IO connections: string representation of the non-transformed state representing the polled data from
* modbus that are accepted by this IO connection and thus should be sent to openHAB event bus.
*
* Use asterisk (*) to match all. Use "CHANGED" (case-insensitive) (applicable only with read connections) to
* trigger only on changed values. With read connections, use "default" to refer to * or CHANGED depending on
* updateunchangeditems slave setting. With write connections, use "default" to refer to *.
*/
private String trigger;
/**
* Object representing transformation for the command or state
*/
private Transformation transformation;
/**
* Use "default" to use slave's value type when interpreting data. Use any other known value type (e.g. int32) to
* override the value type.
*/
private String valueType;
/**
* Previously polled state(s) of Item, converted to state as defined by ItemIOConnection. Initialized to null so
* that
* UnDefType.UNDEF (which might be transmitted
* in case of errors)
* is considered unequal to the initial value.
*/
private State polledState = null;
/**
* Relative poll number of this IO connection for comparing poll times of different IO connections. No two instances
* of {@link ItemIOConnection} have the same poll number.
*
* Value of zero is used for connections that have no polls at all yet.
*/
private long pollNumber = 0;
/**
* Global number indicating how many polls have taken place by all instances of ItemIOConnection (plus one).
*/
private static AtomicLong globalPollNumber = new AtomicLong(1);
public ItemIOConnection(String slaveName, int index, ItemIOConnection.IOType type) {
this.slaveName = slaveName;
this.index = index;
this.type = type;
this.trigger = TRIGGER_DEFAULT;
this.transformation = Transformation.IDENTITY_TRANSFORMATION;
this.valueType = VALUETYPE_DEFAULT;
}
public ItemIOConnection(String slaveName, int index, ItemIOConnection.IOType type, String trigger) {
this(slaveName, index, type);
this.trigger = trigger;
this.transformation = Transformation.IDENTITY_TRANSFORMATION;
this.valueType = VALUETYPE_DEFAULT;
}
public ItemIOConnection(String slaveName, int index, ItemIOConnection.IOType type, String trigger,
Transformation transformation, String valueType) {
this(slaveName, index, type, trigger);
this.transformation = transformation;
this.valueType = valueType;
}
public String getSlaveName() {
return slaveName;
}
public int getIndex() {
return index;
}
public ItemIOConnection.IOType getType() {
return type;
}
public String getTrigger() {
return trigger;
}
/**
* Whether trigger equals <code>TRIGGER_DEFAULT</code> (case-insensitive comparison)
*
* @return
*/
private boolean isTriggerDefault() {
return TRIGGER_DEFAULT.equalsIgnoreCase(this.trigger);
}
/**
* Whether trigger equals <code>POLL_STATE_CHANGE_TRIGGER</code> (case-insensitive comparison)
*
*
* @return
*/
private boolean isTriggerOnPolledStateChange() {
return POLL_STATE_CHANGE_TRIGGER.equalsIgnoreCase(trigger);
}
public String getEffectiveValueType(String defaultValueType) {
return VALUETYPE_DEFAULT.equalsIgnoreCase(this.valueType) ? defaultValueType : this.valueType;
}
public Transformation getTransformation() {
return transformation;
}
public String getValueType() {
return valueType;
}
public State getPreviouslyPolledState() {
return polledState;
}
/**
* Return a number representing the relative time of the poll (greater number is more recent poll).
*
* Poll number of zero means that no polls have taken place.
*
* Poll numbers over all ItemIOConnections are guaranteed to be in order.
*
*/
public long getPollNumber() {
return pollNumber;
}
public void setPreviouslyPolledState(State state) {
this.pollNumber = ItemIOConnection.globalPollNumber.getAndIncrement();
this.polledState = state;
}
/**
* Check if this configuration "supports" the given State.
*
* If return value is true, the processing should continue with this {@link ItemIOConnection}
*
* @param state
* for which to check if we can process.
* @param changed
* whether values was changed
* @param slaveUpdateUnchanged
* whether to update unchanged if this.trigger is default
* @return true if processing is supported.
*/
public boolean supportsState(State state, boolean changed, boolean slaveUpdateUnchanged) {
if (this.type.equals(IOType.COMMAND)) {
return false;
} else if (getTrigger().equals("*")) {
return true;
} else if (isTriggerDefault()) {
if (changed) {
// Value changed, "default" trigger is to update the state
return true;
} else {
// Value not changed, update only if slave updates unchanged items
return slaveUpdateUnchanged;
}
} else if (isTriggerOnPolledStateChange()) {
return changed;
} else {
return trigger.equalsIgnoreCase(state.toString());
}
}
/**
* Check if this configuration supports the given Command.
*
* If return value is true, the processing should continue with this {@link ItemIOConnection}
*
* @param command
* for which to check if we can process.
* @return true if processing is supported.
*/
public boolean supportsCommand(Command command) {
if (this.type.equals(IOType.STATE)) {
return false;
} else if (getTrigger().equals("*")) {
return true;
} else if (getTrigger().equals(TRIGGER_DEFAULT)) {
return true;
} else {
return trigger.equalsIgnoreCase(command.toString());
}
}
/**
* for testing
*/
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (obj == this) {
return true;
}
if (obj.getClass() != getClass()) {
return false;
}
ItemIOConnection other = (ItemIOConnection) obj;
return new EqualsBuilder().append(slaveName, other.slaveName).append(index, other.index)
.append(type, other.type).append(trigger, other.trigger).append(transformation, other.transformation)
.append(valueType, other.valueType).isEquals();
}
/**
* Implemented since equals is there. Just for testing.
*/
@Override
public int hashCode() {
return new HashCodeBuilder(91, 131).append(slaveName).append(index).append(type).append(trigger)
.append(transformation).append(valueType).toHashCode();
}
@Override
public String toString() {
return new ToStringBuilder(this, toStringStyle).append("slaveName", slaveName).append("index", index)
.append("type", type).append("trigger", trigger).append("transformation", transformation)
.append("valueType", valueType).toString();
}
}