package nl.tno.imb.mc; import org.opentrafficsim.imb.IMBException; import org.opentrafficsim.imb.ObjectArrayToIMB; import nl.tno.imb.TByteBuffer; import nl.tno.imb.TConnection; import nl.tno.imb.TEventEntry; import nl.tno.imb.mc.ModelEvent.ModelCommand; /** * IMB Model Control starter. *

* Copyright (c) TNO & Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
* BSD-style license. See OpenTrafficSim License. *

* @version $Revision$, $LastChangedDate$, by $Author$, * initial version Oct 21, 2016
* @author Alexander Verbraeck * @author Peter Knoppers * @author Wouter Schakel */ /** * Model starter. */ public abstract class ModelStarter { /** Controllers root event name. */ static final String CONTROLLERS_ROOT_EVENT_NAME = "Controllers"; /** Default clients root event name. */ static final String CLIENTS_ROOT_EVENT_NAME = "Clients"; /** Federation parameter name. */ static final String FEDERATION_PARAMETER_NAME = "Federation"; /** Data source parameter name. */ static final String DATA_SOURCE_PARAMETER_NAME = "DataSource"; /** Command line remote host switch. */ static final String REMOTE_HOST_SWITCH = "RemoteHost"; /** Default remote host. */ static final String DEFAULT_REMOTE_HOST = "localhost"; /** Command line remote port switch. */ static final String REMOTE_PORT_SWITCH = "RemotePort"; /** Default remote port. */ static final String DEFAULT_REMOTE_PORT = "4000"; /** Command line idle federation switch. */ static final String IDLE_FEDERATION_SWITCH = "IdleFederation"; /** Default idle federation. */ static final String DEFAULT_IDLE_FEDERATION = "USidle"; /** Command line link id switch. */ static final String LINK_ID_SWITCH = "LinkID"; /** Command line controllers event name switch. */ static final String CONTROLLERS_EVENT_NAME_SWITCH = "ControllersEventName"; /** Command line Controller private event name switch. */ static final String CONTROLLER_PRIVATE_EVENT_NAME_SWITCH = "ControllerPrivateEventName"; /** Command line controller switch. */ static final String CONTROLLER_SWITCH = "ControllerName"; /** The default controller. */ static final String DEFAULT_CONTROLLER = "Test"; /** Event name part separator. */ static final String EVENT_NAME_PART_SEPARATOR = "."; /** Command line model name switch. */ static final String MODEL_NAME_SWITCH = "ModelName"; /** Default for model name. */ static final String DEFAULT_MODEL_NAME = "Undefined model name"; /** Command line model id switch. */ static final String MODEL_ID_SWITCH = "ModelID"; /** Default model id (must be numeric). */ static final String DEFAULT_MODEL_ID = "99"; /** Command line model priority switch. */ static final String MODEL_PRIORITY_SWITCH = "ModelPriority"; /** Default model priority. */ static final String DEFAULT_MODEL_PRIORITY = "1"; /** Connection to the IMB hub. */ protected final TConnection connection; /** ??? */ private final String controller; /** ??? */ private final TEventEntry privateModelEvent; /** ??? */ private final TEventEntry controllersEvent; /** ??? */ private final TEventEntry privateControllerEvent; /** This is c# specific */ // private final EventWaitHandle quitApplicationEvent; /** The remote host (IMB server). */ private final String remoteHost; /** The remote IP port. */ private final int remotePort; /** The idle federation. */ private final String idleFederation; /** The controllers event name. */ private final String controllersEventName; /** The controller private event name. */ private final String controllerPrivateEventName; /** State of the model. */ ModelState state; /** Priority. */ int priority; /** Progress. */ int progress; /** * Start the model. * @param parameters ModelParameters; ModelParameters * @param imbConnection TConnection; connection to the IMB hub */ public abstract void startModel(ModelParameters parameters, TConnection imbConnection); /** * Stop the model. */ public abstract void stopModel(); /** * Kill the model; called before this application exits. */ public abstract void quitApplication(); /** * The model must fill in its parameters. * @param parameters ModelParameters; ModelParameters */ public abstract void parameterRequest(ModelParameters parameters); /** * Create a new ModelStarter. * @param args String[]; the command line arguments * @param providedModelName String; name of the model * @param providedModelId int; id of the model * @throws IMBException */ public ModelStarter(final String[] args, final String providedModelName, final int providedModelId) throws IMBException { StandardSettings settings = new StandardSettings(args); this.remoteHost = settings.getSetting(REMOTE_HOST_SWITCH, DEFAULT_REMOTE_HOST); this.remotePort = Integer.parseInt(settings.getSetting(REMOTE_PORT_SWITCH, DEFAULT_REMOTE_PORT)); this.idleFederation = settings.getSetting(IDLE_FEDERATION_SWITCH, DEFAULT_IDLE_FEDERATION); this.controller = settings.getSetting(CONTROLLER_SWITCH, DEFAULT_CONTROLLER); this.controllersEventName = settings.getSetting(CONTROLLERS_EVENT_NAME_SWITCH, this.idleFederation + "." + CONTROLLERS_ROOT_EVENT_NAME); this.controllerPrivateEventName = settings.getSwitch(CONTROLLER_PRIVATE_EVENT_NAME_SWITCH, this.idleFederation + "." + CONTROLLERS_ROOT_EVENT_NAME); long linkId = 0; try { linkId = Long.parseLong(settings.getSwitch(LINK_ID_SWITCH, "0")); } catch (NumberFormatException nfe) { System.err.println("Ignoring bad LinkId"); } String modelName; int modelId; if (null != providedModelName && providedModelName.length() > 0) { modelName = providedModelName; if (providedModelId != 0) { modelId = providedModelId; } else { modelId = Integer.parseInt(settings.getSetting(MODEL_ID_SWITCH, DEFAULT_MODEL_ID)); } } else { modelName = settings.getSetting(MODEL_NAME_SWITCH, DEFAULT_MODEL_NAME); modelId = Integer.parseInt(settings.getSetting(MODEL_ID_SWITCH, DEFAULT_MODEL_ID)); } System.out.println("IMB " + this.remoteHost + ":" + this.getRemotePort()); System.out.println("Controller " + this.controller); System.out.println("ControllersEventName " + this.controllersEventName); System.out.println("ControllerPrivateEventName " + this.controllerPrivateEventName); System.out.println("LinkID " + linkId); System.out.println("ModelName " + modelName); System.out.println("ModelID " + modelId); this.connection = new TConnection(this.remoteHost, this.getRemotePort(), modelName, modelId, ""); if (!this.connection.isConnected()) { throw new IMBException("Could not connect to " + this.remoteHost + ":" + this.getRemotePort()); } this.privateModelEvent = this.connection.subscribe(this.controllerPrivateEventName + EVENT_NAME_PART_SEPARATOR + modelName + EVENT_NAME_PART_SEPARATOR + String.format("%08x", this.connection.getUniqueClientID()), false); this.privateModelEvent.onNormalEvent = new TEventEntry.TOnNormalEvent() { @Override public void dispatch(final TEventEntry aEvent, final TByteBuffer aPayload) { try { handleControlEvents(aEvent, aPayload); } catch (IMBException exception) { exception.printStackTrace(); } } }; this.controllersEvent = this.connection.subscribe(this.controllersEventName, false); this.controllersEvent.onNormalEvent = new TEventEntry.TOnNormalEvent() { @Override public void dispatch(final TEventEntry aEvent, final TByteBuffer aPayload) { try { handleControlEvents(aEvent, aPayload); } catch (IMBException exception) { exception.printStackTrace(); } } }; this.privateControllerEvent = this.connection.subscribe(this.controllerPrivateEventName, false); this.privateControllerEvent.onNormalEvent = new TEventEntry.TOnNormalEvent() { public void dispatch(final TEventEntry aEvent, final TByteBuffer aPayload) { try { handleControlEvents(aEvent, aPayload); } catch (IMBException exception) { exception.printStackTrace(); } } }; signalModelInit(linkId, modelName); this.state = ModelState.IDLE; this.progress = 0; this.priority = Integer.parseInt(settings.getSetting(MODEL_PRIORITY_SWITCH, DEFAULT_MODEL_PRIORITY)); signalModelNew(""); } /** * Start a model. * @param parameters ModelParameters; the parameters needed to start the model * @throws IMBException ... */ public void doStartModel(ModelParameters parameters) throws IMBException { if (parameters.parameterExists(FEDERATION_PARAMETER_NAME)) { try { this.connection.setFederation((String) parameters.getParameterValue(FEDERATION_PARAMETER_NAME)); } catch (IMBException exception) { exception.printStackTrace(); // cannot happen; we just checked that is exists } } signalModelState(ModelState.BUSY); startModel(parameters, this.connection); } /** * Stop the model and go to idle state. * @throws IMBException ... */ public void doStopModel() throws IMBException { stopModel(); signalModelProgress(0); signalModelState(ModelState.IDLE); } /** * Terminate the application. */ public void doQuitApplication() { quitApplication(); try { signalModelExit(); } catch (IMBException exception) { exception.printStackTrace(); } System.out.println("Closing IMB connection..."); this.connection.close(); System.out.println("Exiting..."); System.exit(0); // TODO more gently } /** * Execute a control event. * @param event TEventEntry; the event * @param payload TByteBuffer; details of the event * @throws IMBException when the payload is malformed. */ void handleControlEvents(TEventEntry event, TByteBuffer payload) throws IMBException { final int command = payload.readInt32(); final ModelCommand modelCommand = ModelCommand.byValue(command); switch (modelCommand) { case MODEL: { int action = payload.readInt32(); switch (action) { case TEventEntry.ACTION_CHANGE: { ChangeEvent modelChange = new ChangeEvent(payload); if (this.connection.getUniqueClientID() == modelChange.uid) { switch (modelChange.state) { case LOCK: if (ModelState.IDLE == this.state) { this.state = ModelState.LOCK; } break; case IDLE: if (ModelState.LOCK == this.state) { this.state = ModelState.IDLE; } break; default: System.err .println("Received unsupported external model state change: " + modelChange.state); break; } } break; } default: System.err.println("Ignoring action command " + action); } break; } case REQUEST_DEFAULT_PARAMETERS: { String returnEventName = payload.readString(); if (payload.getReadAvailable() > 0) { ModelParameters modelParameters = new ModelParameters(payload); parameterRequest(modelParameters); // let it be modified this.connection.signalEvent(returnEventName, TEventEntry.EK_NORMAL_EVENT, ObjectArrayToIMB.objectArrayToIMBPayload(new Object[] {ModelCommand.DEFAULT_PARAMETERS.getValue(), this.connection.getUniqueClientID(), modelParameters}), false); } break; } case CLAIM: { int uid = payload.readInt32(); if (this.connection.getUniqueClientID() == uid) { ModelParameters modelParameters = new ModelParameters(payload); doStartModel(modelParameters); } break; } case UNCLAIM: if (this.connection.getUniqueClientID() == payload.readInt32()) { doStopModel(); } break; case QUIT_APPLICATION: if (this.connection.getUniqueClientID() == payload.readInt32()) { doQuitApplication(); } break; case REQUEST_MODELS: signalModelNew(payload.readString()); break; default: // System.err.println("Ignoring unhandled control event " + modelCommand); break; } } /** * Report that the model has exited. * @throws IMBException ... */ private void signalModelExit() throws IMBException { this.controllersEvent.signalEvent(TEventEntry.EK_NORMAL_EVENT, ObjectArrayToIMB.objectArrayToIMBPayload( new Object[] {ModelCommand.MODEL.getValue(), TEventEntry.ACTION_DELETE, this.connection.getUniqueClientID()}) .getBuffer()); } /** * Report that the model has been initialized. * @param linkId long; the link Id * @param modelName String; the name of the model * @throws IMBException ... */ private void signalModelInit(final long linkId, final String modelName) throws IMBException { this.privateControllerEvent.signalEvent(TEventEntry.EK_NORMAL_EVENT, ObjectArrayToIMB.objectArrayToIMBPayload(new Object[] {ModelCommand.INIT.getValue()}).getBuffer()); } /** * Report that a new model has been constructed. * @param eventName String; name of the event that will be transmitted * @throws IMBException ... */ private void signalModelNew(final String eventName) throws IMBException { NewEvent newEvent = new NewEvent(this.connection.getUniqueClientID(), this.connection.getOwnerName(), this.controller, this.priority, this.state, this.connection.getFederation(), this.privateModelEvent.getEventName(), this.privateControllerEvent.getEventName()); TByteBuffer payload = null; payload = ObjectArrayToIMB .objectArrayToIMBPayload(new Object[] {ModelCommand.MODEL.getValue(), TEventEntry.ACTION_NEW, newEvent}); if (eventName.length() == 0) { this.controllersEvent.signalEvent(TEventEntry.EK_NORMAL_EVENT, payload.getBuffer()); } else { this.connection.signalEvent(eventName, TEventEntry.EK_NORMAL_EVENT, payload, false); } if (0 != this.progress) { payload = ObjectArrayToIMB.objectArrayToIMBPayload( new Object[] {ModelCommand.PROGRESS.getValue(), this.connection.getUniqueClientID(), this.progress}); if (eventName.length() == 0) { this.controllersEvent.signalEvent(TEventEntry.EK_NORMAL_EVENT, payload.getBuffer()); } else { this.connection.signalEvent(eventName, TEventEntry.EK_NORMAL_EVENT, payload, false); } } } /** * Report a new progress value. * @param currentProgress int; the new progress value * @throws IMBException */ public void signalModelProgress(int currentProgress) throws IMBException { this.controllersEvent.signalEvent(TEventEntry.EK_NORMAL_EVENT, ObjectArrayToIMB.objectArrayToIMBPayload( new Object[] {ModelCommand.PROGRESS.getValue(), this.connection.getUniqueClientID(), currentProgress}) .getBuffer()); this.progress = currentProgress; } /** * Inform a federation about a state change. * @param newState ModelState; the new state * @param federation String; the federation * @throws IMBException ... */ public void signalModelState(final ModelState newState, final String federation) throws IMBException { ChangeEvent modelChangeEvent = new ChangeEvent(this.connection.getUniqueClientID(), newState, federation); this.controllersEvent.signalEvent(TEventEntry.EK_NORMAL_EVENT, ObjectArrayToIMB .objectArrayToIMBPayload( new Object[] {ModelCommand.MODEL.getValue(), TEventEntry.ACTION_CHANGE, modelChangeEvent}) .getBuffer()); this.state = newState; System.out.println("New model state " + newState + " on " + federation); } /** * Inform our federation about a state change. * @param newState ModelState; the new state * @throws IMBException ... */ public void signalModelState(final ModelState newState) throws IMBException { signalModelState(newState, this.connection.getFederation()); } /** * Retrieve the remote port number. * @return int; the remote port number */ public int getRemotePort() { return this.remotePort; } /** * Retrieve the remote host name. * @return String; the name of the remote host */ public String getRemoteHost() { return this.remoteHost; } }