All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.eclipse.basyx.models.controlcomponent.ControlComponent Maven / Gradle / Ivy

/*******************************************************************************
 * Copyright (C) 2021 the Eclipse BaSyx Authors
 * 
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 * 
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 * 
 * SPDX-License-Identifier: MIT
 ******************************************************************************/
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 {

	// 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 {
		/**
		 * 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> operations = new HashMap<>();

	/**
	 * Saved occupier ID in case of local occupier overwrite
	 */
	protected String savedOccupierID = null;

	/**
	 * Status map
	 */
	protected Map status = null;

	/**
	 * Changed control component state listeners
	 */
	protected Collection listeners = new LinkedList<>();

	/**
	 * Constructor
	 */
	@SuppressWarnings("unchecked")
	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 & Serializable) (v) -> {
			freeControlComponent((String) v[0]);
			return null;
		});
		operations.put(OPERATION_OCCUPY, (Function & Serializable) (v) -> {
			occupyControlComponent((String) v[0]);
			return null;
		});
		operations.put(OPERATION_PRIORITY, (Function & Serializable) (v) -> {
			priorityOccupation((String) v[0]);
			return null;
		});
		operations.put(OPERATION_AUTO, (Function & Serializable) (v) -> {
			this.setExecutionMode(ExecutionMode.AUTO);
			return null;
		});
		operations.put(OPERATION_MANUAL, (Function & Serializable) (v) -> {
			this.setExecutionMode(ExecutionMode.MANUAL);
			return null;
		});
		operations.put(OPERATION_START, (Function & Serializable) (v) -> {
			this.changeExecutionState(ExecutionOrder.START.getValue());
			return null;
		});
		operations.put(OPERATION_RESET, (Function & Serializable) (v) -> {
			this.changeExecutionState(ExecutionOrder.RESET.getValue());
			return null;
		});
		operations.put(OPERATION_ABORT, (Function & Serializable) (v) -> {
			this.changeExecutionState(ExecutionOrder.ABORT.getValue());
			return null;
		});
		operations.put(OPERATION_STOP, (Function & Serializable) (v) -> {
			this.changeExecutionState(ExecutionOrder.STOP.getValue());
			return null;
		});
		operations.put(OPERATION_CLEAR, (Function & 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);
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy