blob: 675742ffccd035e9ec96d8387569ba01cebf255c [file] [log] [blame]
package org.eclipse.basyx.models.controlcomponent;
import java.io.Serializable;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.function.Function;
/**
* BaSys 4.0 control component interface. This is a VAB object that cannot be serialized.
*
* @author kuhn
*
*/
public abstract class ControlComponent extends HashMap<String, Object> {
// Miscellaneous String constants
public static final String STATUS = "STATUS";
public static final String OPERATIONS = "OPERATIONS";
// String constants for operations
public static final String OPERATION_CLEAR = "CLEAR";
public static final String OPERATION_STOP = "STOP";
public static final String OPERATION_ABORT = "ABORT";
public static final String OPERATION_RESET = "RESET";
public static final String OPERATION_START = "START";
public static final String OPERATION_MANUAL = "MANUAL";
public static final String OPERATION_AUTO = "AUTO";
public static final String OPERATION_PRIORITY = "PRIO";
public static final String OPERATION_OCCUPY = "OCCUPY";
public static final String OPERATION_FREE = "FREE";
// String constants for setting execution state
public static final String CMD = "cmd";
// String constants for status
public static final String ERROR_STATE = "ER";
public static final String WORK_STATE = "WORKST";
public static final String OP_MODE = "OPMODE";
public static final String EX_STATE = "EXST";
public static final String EX_MODE = "EXMODE";
public static final String OCCUPIER = "OCCUPIER";
public static final String OCCUPATION_STATE = "OCCST";
/**
* The status map implements the service/ substructure of the control component structure. It also
* indicates variable changes via callbacks of the outer class.
*
* @author kuhn
*
*/
class StatusMap extends HashMap<String, Object> {
/**
* Version number of serialized instances
*/
private static final long serialVersionUID = 1L;
/**
* Constructor
*/
public StatusMap() {
// Populate control component "status" sub structure
put(OCCUPATION_STATE, OccupationState.FREE.getValue()); // Occupation state: FREE
put(OCCUPIER, ""); // Occupier: none
put(EX_MODE, ExecutionMode.AUTO.getValue()); // Execution mode: AUTO
put(EX_STATE, ExecutionState.IDLE.getValue()); // Execution state: IDLE
put(OP_MODE, ""); // Component specific operation mode (e.g. active service)
put(WORK_STATE, ""); // Component specific work state
put(ERROR_STATE, ""); // Component error state
}
/**
* Update an value
*
* @return Added value
*/
@Override
public Object put(String key, Object parValue) {
// Value to be put in map
Object value = parValue;
// - Eventually we have to change the value to be put into the variable
switch(key) {
case EX_STATE: value = filterExecutionState(value.toString()); break;
case OP_MODE: value = filterOperationMode(value.toString()); break;
}
// Invoke base implementation
Object result = super.put(key, value);
// Indicate value change
for (ControlComponentChangeListener listener: listeners) listener.onVariableChange(key, value);
// Indicate specific changes to callback operations of control component
switch(key) {
case OCCUPATION_STATE: for (ControlComponentChangeListener listener: listeners) listener.onNewOccupationState(OccupationState.byValue((int) value)); break;
case OCCUPIER: for (ControlComponentChangeListener listener: listeners) listener.onNewOccupier(value.toString()); break;
case EX_MODE: for (ControlComponentChangeListener listener: listeners) listener.onChangedExecutionMode(ExecutionMode.byValue((int) value)); break;
case EX_STATE: for (ControlComponentChangeListener listener: listeners) listener.onChangedExecutionState(ExecutionState.byValue(value.toString())); break;
case OP_MODE: for (ControlComponentChangeListener listener: listeners) listener.onChangedOperationMode(value.toString()); break;
case WORK_STATE: for (ControlComponentChangeListener listener: listeners) listener.onChangedWorkState(value.toString()); break;
case ERROR_STATE: for (ControlComponentChangeListener listener: listeners) listener.onChangedErrorState(value.toString()); break;
}
// Return result
return result;
}
}
/**
* Version number of serialized instances
*/
private static final long serialVersionUID = 1L;
/**
* Operations map
*/
protected Map<String, Function<?, ?>> operations = new HashMap<>();
/**
* Saved occupier ID in case of local occupier overwrite
*/
protected String savedOccupierID = null;
/**
* Status map
*/
protected Map<String, Object> status = null;
/**
* Changed control component state listeners
*/
protected Collection<ControlComponentChangeListener> listeners = new LinkedList<>();
/**
* Constructor
*/
public ControlComponent() {
// Add control component output signals to map
// - "status" sub structure
status = new StatusMap();
put(STATUS, status);
// Input signals
// - Command / stores last command
put(CMD, ""); // No command
// Operations
// - Add "operations" sub structure
put(OPERATIONS, operations);
// - Populate service operations
operations.put(OPERATION_FREE, (Function<Object[], Void> & Serializable) (v) -> {
freeControlComponent((String) v[0]);
return null;
});
operations.put(OPERATION_OCCUPY, (Function<Object[], Void> & Serializable) (v) -> {
occupyControlComponent((String) v[0]);
return null;
});
operations.put(OPERATION_PRIORITY, (Function<Object[], Void> & Serializable) (v) -> {
priorityOccupation((String) v[0]);
return null;
});
operations.put(OPERATION_AUTO, (Function<Object, Void> & Serializable) (v) -> {
this.setExecutionMode(ExecutionMode.AUTO);
return null;
});
operations.put(OPERATION_MANUAL, (Function<Object, Void> & Serializable) (v) -> {
this.setExecutionMode(ExecutionMode.MANUAL);
return null;
});
operations.put(OPERATION_START, (Function<Object, Void> & Serializable) (v) -> {
this.changeExecutionState(ExecutionOrder.START.getValue());
return null;
});
operations.put(OPERATION_RESET, (Function<Object, Void> & Serializable) (v) -> {
this.changeExecutionState(ExecutionOrder.RESET.getValue());
return null;
});
operations.put(OPERATION_ABORT, (Function<Object, Void> & Serializable) (v) -> {
this.changeExecutionState(ExecutionOrder.ABORT.getValue());
return null;
});
operations.put(OPERATION_STOP, (Function<Object, Void> & Serializable) (v) -> {
this.changeExecutionState(ExecutionOrder.STOP.getValue());
return null;
});
operations.put(OPERATION_CLEAR, (Function<Object, Void> & Serializable) (v) -> {
this.changeExecutionState(ExecutionOrder.CLEAR.getValue());
return null;
});
}
/**
* Optionally filter a set execution state. This function is always invoked when an execution state changes
*/
protected String filterExecutionState(String exState) {
// Do nothing here
return exState;
}
/**
* Optionally filter a set operation mode. This function is always invoked when an operation mode changes
*/
protected String filterOperationMode(String opMode) {
// Do nothing here
return opMode;
}
/**
* Add ControlComponentChangeListener
*/
public void addControlComponentChangeListener(ControlComponentChangeListener listener) {
listeners.add(listener);
}
/**
* Remove ControlComponentChangeListener
*/
public void removeControlComponentChangeListener(ControlComponentChangeListener listener) {
listeners.remove(listener);
}
/**
* Update an value
*
* @return Added value
*/
@Override
public Object put(String key, Object value) {
// Invoke base implementation
Object result = super.put(key, value);
// Indicate value change
for (ControlComponentChangeListener listener: listeners) listener.onVariableChange(key, value);
// Process variable changes
switch(key) {
case CMD: changeExecutionState(value.toString()); break;
}
// Return result
return result;
}
/**
* Helper method - free this control component
*/
private void freeControlComponent(String senderId) {
// Update occupier if sender is occupier
if (senderId.equals(this.getOccupierID())) {
// Get occupier from last occupier and reset last occupier
// Component is free if last occupier is empty, occupied otherwise
if (this.getOccupierID().isEmpty()) this.setOccupationState(OccupationState.FREE); else this.setOccupationState(OccupationState.OCCUPIED);
}
}
/**
* Helper method - occupy this control component if it is free
*/
private void occupyControlComponent(String occupier) {
// Update occupier if component is FREE
if (this.getOccupationState().equals(OccupationState.FREE)) {this.setOccupierID(occupier); this.setOccupationState(OccupationState.OCCUPIED);}
}
/**
* Helper method - priority occupation of this component
*/
private void priorityOccupation(String occupier) {
// Occupy component if component is FREE or OCCUPIED
if ((this.getOccupationState().equals(OccupationState.FREE)) || (this.getOccupationState().equals(OccupationState.OCCUPIED))) {
this.setOccupierID(occupier);
this.setOccupationState(OccupationState.PRIORITY);
}
}
/**
* Change execution state based on execution order
*/
private void changeExecutionState(String orderString) {
// Do not react on empty values
if (orderString.isEmpty()) return;
// Get execution order based on order string
ExecutionOrder order = ExecutionOrder.byValue(orderString);
// Check if execution order leads to valid state in current state
switch(ExecutionState.byValue(getExecutionState())) {
case IDLE:
// Process expected orders
if (order.equals(ExecutionOrder.START)) {this.setExecutionState(ExecutionState.STARTING.getValue()); break;}
if (order.equals(ExecutionOrder.STOP)) {this.setExecutionState(ExecutionState.STOPPING.getValue()); break;}
if (order.equals(ExecutionOrder.ABORT)) {this.setExecutionState(ExecutionState.ABORTING.getValue()); break;}
// Unexpected order in this state
throw new RuntimeException("Unexpected command "+orderString+" in state "+getExecutionState());
case STARTING:
if (order.equals(ExecutionOrder.STOP)) {this.setExecutionState(ExecutionState.STOPPING.getValue()); break;}
if (order.equals(ExecutionOrder.ABORT)) {this.setExecutionState(ExecutionState.ABORTING.getValue()); break;}
// Unexpected order in this state
throw new RuntimeException("Unexpected command "+orderString+" in state "+getExecutionState());
case EXECUTE:
// Process expected orders
if (order.equals(ExecutionOrder.COMPLETE)) {this.setExecutionState(ExecutionState.COMPLETING.getValue()); break;}
if (order.equals(ExecutionOrder.HOLD)) {this.setExecutionState(ExecutionState.HOLDING.getValue()); break;}
if (order.equals(ExecutionOrder.SUSPEND)) {this.setExecutionState(ExecutionState.SUSPENDING.getValue()); break;}
if (order.equals(ExecutionOrder.STOP)) {this.setExecutionState(ExecutionState.STOPPING.getValue()); break;}
if (order.equals(ExecutionOrder.ABORT)) {this.setExecutionState(ExecutionState.ABORTING.getValue()); break;}
// Unexpected order in this state
throw new RuntimeException("Unexpected command "+orderString+" in state "+getExecutionState());
case COMPLETING:
if (order.equals(ExecutionOrder.STOP)) {this.setExecutionState(ExecutionState.STOPPING.getValue()); break;}
if (order.equals(ExecutionOrder.ABORT)) {this.setExecutionState(ExecutionState.ABORTING.getValue()); break;}
// Unexpected order in this state
throw new RuntimeException("Unexpected command "+orderString+" in state "+getExecutionState());
case COMPLETE:
if (order.equals(ExecutionOrder.RESET)) {this.setExecutionState(ExecutionState.RESETTING.getValue()); break;}
if (order.equals(ExecutionOrder.STOP)) {this.setExecutionState(ExecutionState.STOPPING.getValue()); break;}
if (order.equals(ExecutionOrder.ABORT)) {this.setExecutionState(ExecutionState.ABORTING.getValue()); break;}
// Unexpected order in this state
throw new RuntimeException("Unexpected command "+orderString+" in state "+getExecutionState());
case RESETTING:
if (order.equals(ExecutionOrder.STOP)) {this.setExecutionState(ExecutionState.STOPPING.getValue()); break;}
if (order.equals(ExecutionOrder.ABORT)) {this.setExecutionState(ExecutionState.ABORTING.getValue()); break;}
// Unexpected order in this state
throw new RuntimeException("Unexpected command "+orderString+" in state "+getExecutionState());
case HOLDING:
if (order.equals(ExecutionOrder.STOP)) {this.setExecutionState(ExecutionState.STOPPING.getValue()); break;}
if (order.equals(ExecutionOrder.ABORT)) {this.setExecutionState(ExecutionState.ABORTING.getValue()); break;}
// Unexpected order in this state
throw new RuntimeException("Unexpected command "+orderString+" in state "+getExecutionState());
case HELD:
if (order.equals(ExecutionOrder.UNHOLD)) {this.setExecutionState(ExecutionState.UNHOLDING.getValue()); break;}
if (order.equals(ExecutionOrder.STOP)) {this.setExecutionState(ExecutionState.STOPPING.getValue()); break;}
if (order.equals(ExecutionOrder.ABORT)) {this.setExecutionState(ExecutionState.ABORTING.getValue()); break;}
// Unexpected order in this state
throw new RuntimeException("Unexpected command "+orderString+" in state "+getExecutionState());
case UNHOLDING:
if (order.equals(ExecutionOrder.STOP)) {this.setExecutionState(ExecutionState.STOPPING.getValue()); break;}
if (order.equals(ExecutionOrder.ABORT)) {this.setExecutionState(ExecutionState.ABORTING.getValue()); break;}
// Unexpected order in this state
throw new RuntimeException("Unexpected command "+orderString+" in state "+getExecutionState());
case SUSPENDING:
if (order.equals(ExecutionOrder.STOP)) {this.setExecutionState(ExecutionState.STOPPING.getValue()); break;}
if (order.equals(ExecutionOrder.ABORT)) {this.setExecutionState(ExecutionState.ABORTING.getValue()); break;}
// Unexpected order in this state
throw new RuntimeException("Unexpected command "+orderString+" in state "+getExecutionState());
case SUSPENDED:
if (order.equals(ExecutionOrder.UNSUSPEND)) {this.setExecutionState(ExecutionState.UNSUSPENDING.getValue()); break;}
if (order.equals(ExecutionOrder.STOP)) {this.setExecutionState(ExecutionState.STOPPING.getValue()); break;}
if (order.equals(ExecutionOrder.ABORT)) {this.setExecutionState(ExecutionState.ABORTING.getValue()); break;}
// Unexpected order in this state
throw new RuntimeException("Unexpected command "+orderString+" in state "+getExecutionState());
case UNSUSPENDING:
if (order.equals(ExecutionOrder.STOP)) {this.setExecutionState(ExecutionState.STOPPING.getValue()); break;}
if (order.equals(ExecutionOrder.ABORT)) {this.setExecutionState(ExecutionState.ABORTING.getValue()); break;}
// Unexpected order in this state
throw new RuntimeException("Unexpected command "+orderString+" in state "+getExecutionState());
case STOPPING:
if (order.equals(ExecutionOrder.ABORT)) {this.setExecutionState(ExecutionState.ABORTING.getValue()); break;}
// Unexpected order in this state
throw new RuntimeException("Unexpected command "+orderString+" in state "+getExecutionState());
case STOPPED:
if (order.equals(ExecutionOrder.RESET)) {this.setExecutionState(ExecutionState.RESETTING.getValue()); break;}
if (order.equals(ExecutionOrder.ABORT)) {this.setExecutionState(ExecutionState.ABORTING.getValue()); break;}
// Unexpected order in this state
throw new RuntimeException("Unexpected command "+orderString+" in state "+getExecutionState());
case ABORTED:
if (order.equals(ExecutionOrder.CLEAR)) {this.setExecutionState(ExecutionState.CLEARING.getValue()); break;}
// Unexpected order in this state
throw new RuntimeException("Unexpected command "+orderString+" in state "+getExecutionState());
case CLEARING:
if (order.equals(ExecutionOrder.ABORT)) {this.setExecutionState(ExecutionState.ABORTING.getValue()); break;}
// Unexpected order in this state
throw new RuntimeException("Unexpected command "+orderString+" in state "+getExecutionState());
// Received order in unexpected state
default:
// Indicate error
throw new RuntimeException("Unexpected order "+orderString+" in state "+getExecutionState());
}
}
/**
* Finish current execution state (execute 'SC' order). This only works in transition states
*/
public void finishState() {
// Check if state complete message leads to valid state in current state
switch(ExecutionState.byValue(getExecutionState())) {
// Process state changes
case STARTING: this.setExecutionState(ExecutionState.EXECUTE.getValue()); break;
case EXECUTE: this.setExecutionState(ExecutionState.COMPLETING.getValue()); break;
case COMPLETING: this.setExecutionState(ExecutionState.COMPLETE.getValue()); break;
case RESETTING: this.setExecutionState(ExecutionState.IDLE.getValue()); break;
case HOLDING: this.setExecutionState(ExecutionState.HELD.getValue()); break;
case UNHOLDING: this.setExecutionState(ExecutionState.EXECUTE.getValue()); break;
case SUSPENDING: this.setExecutionState(ExecutionState.SUSPENDED.getValue()); break;
case UNSUSPENDING: this.setExecutionState(ExecutionState.EXECUTE.getValue()); break;
case STOPPING: this.setExecutionState(ExecutionState.STOPPED.getValue()); break;
case STOPPED: this.setExecutionState(ExecutionState.IDLE.getValue()); break;
case ABORTING: this.setExecutionState(ExecutionState.ABORTED.getValue()); break;
case CLEARING: this.setExecutionState(ExecutionState.STOPPED.getValue()); break;
// Received order in unexpected state
default:
// Indicate error
throw new RuntimeException("Unexpected state complete order in state "+getExecutionState());
}
}
/**
* Get occupation state
*/
public OccupationState getOccupationState() {
// Return occupation state
return OccupationState.byValue((Integer) status.get(OCCUPATION_STATE));
}
/**
* Set occupation state
*/
public void setOccupationState(OccupationState occSt) {
// Update occupation state
status.put(OCCUPATION_STATE, occSt.getValue());
}
/**
* Get occupier ID
*/
public String getOccupierID() {
// If occupier is not set, a null pointer Exception will be thrown when invoking toString(). Return an empty string in this case (=no occupier)
try {
return status.get(OCCUPIER).toString();
} catch (NullPointerException e) {
return "";
}
}
/**
* Set occupier ID
*/
public void setOccupierID(String occId) {
status.put(OCCUPIER, occId);
}
/**
* Get execution mode
*/
public ExecutionMode getExecutionMode() {
// Return execution mode
return ExecutionMode.byValue((Integer) status.get(EX_MODE));
}
/**
* Set execution mode
*/
public void setExecutionMode(ExecutionMode exMode) {
// Return execution mode
status.put(EX_MODE, exMode.getValue());
}
/**
* Get execution state
*/
public String getExecutionState() {
// If member is not set, a null pointer Exception will be thrown when invoking toString(). Return an empty string in this case (=no occupier)
try {
return status.get(EX_STATE).toString();
} catch (NullPointerException e) {
return "";
}
}
/**
* Set execution state
*/
public void setExecutionState(String newSt) {
// System.out.println("Comp: change to:"+newSt);
// Change execution state
status.put(EX_STATE, newSt);
}
/**
* Get operation mode
*/
public String getOperationMode() {
// If member is not set, a null pointer Exception will be thrown when invoking toString(). Return an empty string in this case (=no occupier)
try {
return status.get(OP_MODE).toString();
} catch (NullPointerException e) {
return "";
}
}
/**
* Set operation mode
*/
public void setOperationMode(String opMode) {
// Change operation mode
status.put(OP_MODE, opMode);
}
/**
* Get work state
*/
public String getWorkState() {
// If member is not set, a null pointer Exception will be thrown when invoking toString(). Return an empty string in this case (=no occupier)
try {
return status.get(WORK_STATE).toString();
} catch (NullPointerException e) {
return "";
}
}
/**
* Set work state
*/
public void setWorkState(String workState) {
// Change work state
status.put(WORK_STATE, workState);
}
/**
* Get error state
*/
public String getErrorState() {
// If member is not set, a null pointer Exception will be thrown when invoking toString(). Return an empty string in this case (=no occupier)
try {
return status.get(ERROR_STATE).toString();
} catch (NullPointerException e) {
return "";
}
}
/**
* Set error state
*/
public void setErrorState(String errorState) {
// Change error state
status.put(ERROR_STATE, errorState);
}
/**
* Get last command
*/
public String getCommand() {
// If member is not set, a null pointer Exception will be thrown when invoking toString(). Return an empty string in this case (=no occupier)
try {return get(CMD).toString();} catch (NullPointerException e) {return "";}
}
/**
* Set command
*/
public void setCommand(String cmd) {
// Change last command
put(CMD, cmd);
}
}