View Javadoc
1   package nl.tno.imb.mc;
2   
3   import nl.tno.imb.TByteBuffer;
4   import nl.tno.imb.TConnection;
5   import nl.tno.imb.TEventEntry;
6   import nl.tno.imb.mc.ModelEvent.ModelCommand;
7   
8   import org.opentrafficsim.imb.IMBException;
9   import org.opentrafficsim.imb.ObjectArrayToIMB;
10  
11  /**
12   * IMB Model Control starter.
13   * <p>
14   * Copyright (c) TNO & Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
15   * BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
16   * <p>
17   * @version $Revision$, $LastChangedDate$, by $Author$,
18   *          initial version Oct 21, 2016 <br>
19   * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
20   * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
21   * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
22   */
23  /**
24   * Model starter.
25   */
26  public abstract class ModelStarter
27  {
28      /** Controllers root event name. */
29      static final String CONTROLLERS_ROOT_EVENT_NAME = "Controllers";
30  
31      /** Default clients root event name. */
32      static final String CLIENTS_ROOT_EVENT_NAME = "Clients";
33  
34      /** Federation parameter name. */
35      static final String FEDERATION_PARAMETER_NAME = "Federation";
36  
37      /** Data source parameter name. */
38      static final String DATA_SOURCE_PARAMETER_NAME = "DataSource";
39  
40      /** Command line remote host switch. */
41      static final String REMOTE_HOST_SWITCH = "RemoteHost";
42  
43      /** Default remote host. */
44      static final String DEFAULT_REMOTE_HOST = "localhost";
45  
46      /** Command line remote port switch. */
47      static final String REMOTE_PORT_SWITCH = "RemotePort";
48  
49      /** Default remote port. */
50      static final String DEFAULT_REMOTE_PORT = "4000";
51  
52      /** Command line idle federation switch. */
53      static final String IDLE_FEDERATION_SWITCH = "IdleFederation";
54  
55      /** Default idle federation. */
56      static final String DEFAULT_IDLE_FEDERATION = "USidle";
57  
58      /** Command line link id switch. */
59      static final String LINK_ID_SWITCH = "LinkID";
60  
61      /** Command line controllers event name switch. */
62      static final String CONTROLLERS_EVENT_NAME_SWITCH = "ControllersEventName";
63  
64      /** Command line Controller private event name switch. */
65      static final String CONTROLLER_PRIVATE_EVENT_NAME_SWITCH = "ControllerPrivateEventName";
66  
67      /** Command line controller switch. */
68      static final String CONTROLLER_SWITCH = "ControllerName";
69  
70      /** The default controller. */
71      static final String DEFAULT_CONTROLLER = "Test";
72  
73      /** Event name part separator. */
74      static final String EVENT_NAME_PART_SEPARATOR = ".";
75  
76      /** Command line model name switch. */
77      static final String MODEL_NAME_SWITCH = "ModelName";
78  
79      /** Default for model name. */
80      static final String DEFAULT_MODEL_NAME = "Undefined model name";
81  
82      /** Command line model id switch. */
83      static final String MODEL_ID_SWITCH = "ModelID";
84  
85      /** Default model id (must be numeric). */
86      static final String DEFAULT_MODEL_ID = "99";
87  
88      /** Command line model priority switch. */
89      static final String MODEL_PRIORITY_SWITCH = "ModelPriority";
90  
91      /** Default model priority. */
92      static final String DEFAULT_MODEL_PRIORITY = "1";
93  
94      /** Connection to the IMB hub. */
95      protected final TConnection connection;
96  
97      /** ??? */
98      private final String controller;
99  
100     /** ??? */
101     private final TEventEntry privateModelEvent;
102 
103     /** ??? */
104     private final TEventEntry controllersEvent;
105 
106     /** ??? */
107     private final TEventEntry privateControllerEvent;
108 
109     /** This is c# specific */
110     // private final EventWaitHandle quitApplicationEvent;
111 
112     /** The remote host (IMB server). */
113     private final String remoteHost;
114 
115     /** The remote IP port. */
116     private final int remotePort;
117 
118     /** The idle federation. */
119     private final String idleFederation;
120 
121     /** The controllers event name. */
122     private final String controllersEventName;
123 
124     /** The controller private event name. */
125     private final String controllerPrivateEventName;
126 
127     /** State of the model. */
128     ModelState state;
129 
130     /** Priority. */
131     int priority;
132 
133     /** Progress. */
134     int progress;
135 
136     /**
137      * Start the model.
138      * @param parameters ModelParameters
139      * @param imbConnection TConnection; connection to the IMB hub
140      */
141     public abstract void startModel(ModelParameters parameters, TConnection imbConnection);
142 
143     /**
144      * Stop the model.
145      */
146     public abstract void stopModel();
147 
148     /**
149      * Kill the model; called before this application exits.
150      */
151     public abstract void quitApplication();
152 
153     /**
154      * The model must fill in its parameters.
155      * @param parameters ModelParameters
156      */
157     public abstract void parameterRequest(ModelParameters parameters);
158 
159     /**
160      * Create a new ModelStarter.
161      * @param args String[]; the command line arguments
162      * @param providedModelName String; name of the model
163      * @param providedModelId int; id of the model
164      * @throws IMBException
165      */
166     public ModelStarter(final String[] args, final String providedModelName, final int providedModelId) throws IMBException
167     {
168         StandardSettings settings = new StandardSettings(args);
169         this.remoteHost = settings.getSetting(REMOTE_HOST_SWITCH, DEFAULT_REMOTE_HOST);
170         this.remotePort = Integer.parseInt(settings.getSetting(REMOTE_PORT_SWITCH, DEFAULT_REMOTE_PORT));
171         this.idleFederation = settings.getSetting(IDLE_FEDERATION_SWITCH, DEFAULT_IDLE_FEDERATION);
172         this.controller = settings.getSetting(CONTROLLER_SWITCH, DEFAULT_CONTROLLER);
173         this.controllersEventName =
174                 settings.getSetting(CONTROLLERS_EVENT_NAME_SWITCH, this.idleFederation + "." + CONTROLLERS_ROOT_EVENT_NAME);
175         this.controllerPrivateEventName =
176                 settings.getSwitch(CONTROLLER_PRIVATE_EVENT_NAME_SWITCH, this.idleFederation + "."
177                         + CONTROLLERS_ROOT_EVENT_NAME);
178         long linkId = 0;
179         try
180         {
181             linkId = Long.parseLong(settings.getSwitch(LINK_ID_SWITCH, "0"));
182         }
183         catch (NumberFormatException nfe)
184         {
185             System.err.println("Ignoring bad LinkId");
186         }
187         String modelName;
188         int modelId;
189         if (null != providedModelName && providedModelName.length() > 0)
190         {
191             modelName = providedModelName;
192             if (providedModelId != 0)
193             {
194                 modelId = providedModelId;
195             }
196             else
197             {
198                 modelId = Integer.parseInt(settings.getSetting(MODEL_ID_SWITCH, DEFAULT_MODEL_ID));
199             }
200         }
201         else
202         {
203             modelName = settings.getSetting(MODEL_NAME_SWITCH, DEFAULT_MODEL_NAME);
204             modelId = Integer.parseInt(settings.getSetting(MODEL_ID_SWITCH, DEFAULT_MODEL_ID));
205         }
206         System.out.println("IMB " + this.remoteHost + ":" + this.getRemotePort());
207         System.out.println("Controller " + this.controller);
208         System.out.println("ControllersEventName " + this.controllersEventName);
209         System.out.println("ControllerPrivateEventName " + this.controllerPrivateEventName);
210         System.out.println("LinkID " + linkId);
211         System.out.println("ModelName " + modelName);
212         System.out.println("ModelID " + modelId);
213         this.connection = new TConnection(this.remoteHost, this.getRemotePort(), modelName, modelId, "");
214         if (!this.connection.isConnected())
215         {
216             throw new IMBException("Could not connect to " + this.remoteHost + ":" + this.getRemotePort());
217         }
218         this.privateModelEvent =
219                 this.connection.subscribe(this.controllerPrivateEventName + EVENT_NAME_PART_SEPARATOR + modelName
220                         + EVENT_NAME_PART_SEPARATOR + String.format("%08x", this.connection.getUniqueClientID()), false);
221         this.privateModelEvent.onNormalEvent = new TEventEntry.TOnNormalEvent()
222         {
223             @Override
224             public void dispatch(final TEventEntry aEvent, final TByteBuffer aPayload)
225             {
226                 try
227                 {
228                     handleControlEvents(aEvent, aPayload);
229                 }
230                 catch (IMBException exception)
231                 {
232                     exception.printStackTrace();
233                 }
234             }
235         };
236         this.controllersEvent = this.connection.subscribe(this.controllersEventName, false);
237         this.controllersEvent.onNormalEvent = new TEventEntry.TOnNormalEvent()
238         {
239             @Override
240             public void dispatch(final TEventEntry aEvent, final TByteBuffer aPayload)
241             {
242                 try
243                 {
244                     handleControlEvents(aEvent, aPayload);
245                 }
246                 catch (IMBException exception)
247                 {
248                     exception.printStackTrace();
249                 }
250             }
251         };
252         this.privateControllerEvent = this.connection.subscribe(this.controllerPrivateEventName, false);
253         this.privateControllerEvent.onNormalEvent = new TEventEntry.TOnNormalEvent()
254         {
255             public void dispatch(final TEventEntry aEvent, final TByteBuffer aPayload)
256             {
257                 try
258                 {
259                     handleControlEvents(aEvent, aPayload);
260                 }
261                 catch (IMBException exception)
262                 {
263                     exception.printStackTrace();
264                 }
265             }
266         };
267         signalModelInit(linkId, modelName);
268         this.state = ModelState.IDLE;
269         this.progress = 0;
270         this.priority = Integer.parseInt(settings.getSetting(MODEL_PRIORITY_SWITCH, DEFAULT_MODEL_PRIORITY));
271         signalModelNew("");
272     }
273 
274     /**
275      * Start a model.
276      * @param parameters ModelParameters; the parameters needed to start the model
277      * @throws IMBException ...
278      */
279     public void doStartModel(ModelParameters parameters) throws IMBException
280     {
281         if (parameters.parameterExists(FEDERATION_PARAMETER_NAME))
282         {
283             try
284             {
285                 this.connection.setFederation((String) parameters.getParameterValue(FEDERATION_PARAMETER_NAME));
286             }
287             catch (IMBException exception)
288             {
289                 exception.printStackTrace(); // cannot happen; we just checked that is exists
290             }
291         }
292         signalModelState(ModelState.BUSY);
293         startModel(parameters, this.connection);
294     }
295 
296     /**
297      * Stop the model and go to idle state.
298      * @throws IMBException ...
299      */
300     public void doStopModel() throws IMBException
301     {
302         stopModel();
303         signalModelProgress(0);
304         signalModelState(ModelState.IDLE);
305     }
306 
307     /**
308      * Terminate the application.
309      */
310     public void doQuitApplication()
311     {
312         quitApplication();
313         try
314         {
315             signalModelExit();
316         }
317         catch (IMBException exception)
318         {
319             exception.printStackTrace();
320         }
321         System.out.println("Closing IMB connection...");
322         this.connection.close();
323         System.out.println("Exiting...");
324         System.exit(0); // TODO more gently
325     }
326 
327     /**
328      * Execute a control event.
329      * @param event TEventEntry; the event
330      * @param payload TByteBuffer; details of the event
331      * @throws IMBException when the payload is malformed.
332      */
333     void handleControlEvents(TEventEntry event, TByteBuffer payload) throws IMBException
334     {
335         final int command = payload.readInt32();
336         final ModelCommand modelCommand = ModelCommand.byValue(command);
337         switch (modelCommand)
338         {
339             case MODEL:
340             {
341                 int action = payload.readInt32();
342                 switch (action)
343                 {
344                     case TEventEntry.ACTION_CHANGE:
345                     {
346                         ChangeEvent modelChange = new ChangeEvent(payload);
347                         if (this.connection.getUniqueClientID() == modelChange.uid)
348                         {
349                             switch (modelChange.state)
350                             {
351                                 case LOCK:
352                                     if (ModelState.IDLE == this.state)
353                                     {
354                                         this.state = ModelState.LOCK;
355                                     }
356                                     break;
357 
358                                 case IDLE:
359                                     if (ModelState.LOCK == this.state)
360                                     {
361                                         this.state = ModelState.IDLE;
362                                     }
363                                     break;
364 
365                                 default:
366                                     System.err
367                                             .println("Received unsupported external model state change: " + modelChange.state);
368                                     break;
369 
370                             }
371                         }
372                         break;
373                     }
374                     default:
375                         System.err.println("Ignoring action command " + action);
376                 }
377                 break;
378             }
379             case REQUEST_DEFAULT_PARAMETERS:
380             {
381                 String returnEventName = payload.readString();
382                 if (payload.getReadAvailable() > 0)
383                 {
384                     ModelParameters modelParameters = new ModelParameters(payload);
385                     parameterRequest(modelParameters); // let it be modified
386                     this.connection.signalEvent(
387                             returnEventName,
388                             TEventEntry.EK_NORMAL_EVENT,
389                             ObjectArrayToIMB.objectArrayToIMBPayload(new Object[] { ModelCommand.DEFAULT_PARAMETERS.getValue(),
390                                     this.connection.getUniqueClientID(), modelParameters }), false);
391                 }
392                 break;
393             }
394             case CLAIM:
395             {
396                 int uid = payload.readInt32();
397                 if (this.connection.getUniqueClientID() == uid)
398                 {
399                     ModelParameters modelParameters = new ModelParameters(payload);
400                     doStartModel(modelParameters);
401                 }
402                 break;
403             }
404             case UNCLAIM:
405                 if (this.connection.getUniqueClientID() == payload.readInt32())
406                 {
407                     doStopModel();
408                 }
409                 break;
410 
411             case QUIT_APPLICATION:
412                 if (this.connection.getUniqueClientID() == payload.readInt32())
413                 {
414                     doQuitApplication();
415                 }
416                 break;
417 
418             case REQUEST_MODELS:
419                 signalModelNew(payload.readString());
420                 break;
421 
422             default:
423                 // System.err.println("Ignoring unhandled control event " + modelCommand);
424                 break;
425         }
426     }
427 
428     /**
429      * Report that the model has exited.
430      * @throws IMBException ...
431      */
432     private void signalModelExit() throws IMBException
433     {
434         this.controllersEvent.signalEvent(
435                 TEventEntry.EK_NORMAL_EVENT,
436                 ObjectArrayToIMB.objectArrayToIMBPayload(
437                         new Object[] { ModelCommand.MODEL.getValue(), TEventEntry.ACTION_DELETE,
438                                 this.connection.getUniqueClientID() }).getBuffer());
439     }
440 
441     /**
442      * Report that the model has been initialized.
443      * @param linkId long; the link Id
444      * @param modelName String; the name of the model
445      * @throws IMBException ...
446      */
447     private void signalModelInit(final long linkId, final String modelName) throws IMBException
448     {
449         this.privateControllerEvent.signalEvent(TEventEntry.EK_NORMAL_EVENT,
450                 ObjectArrayToIMB.objectArrayToIMBPayload(new Object[] { ModelCommand.INIT.getValue() }).getBuffer());
451     }
452 
453     /**
454      * Report that a new model has been constructed.
455      * @param eventName String; name of the event that will be transmitted
456      * @throws IMBException ...
457      */
458     private void signalModelNew(final String eventName) throws IMBException
459     {
460         NewEvent newEvent =
461                 new NewEvent(this.connection.getUniqueClientID(), this.connection.getOwnerName(), this.controller,
462                         this.priority, this.state, this.connection.getFederation(), this.privateModelEvent.getEventName(),
463                         this.privateControllerEvent.getEventName());
464         TByteBuffer payload = null;
465         payload =
466                 ObjectArrayToIMB.objectArrayToIMBPayload(new Object[] { ModelCommand.MODEL.getValue(), TEventEntry.ACTION_NEW,
467                         newEvent });
468         if (eventName.length() == 0)
469         {
470             this.controllersEvent.signalEvent(TEventEntry.EK_NORMAL_EVENT, payload.getBuffer());
471         }
472         else
473         {
474             this.connection.signalEvent(eventName, TEventEntry.EK_NORMAL_EVENT, payload, false);
475         }
476         if (0 != this.progress)
477         {
478             payload =
479                     ObjectArrayToIMB.objectArrayToIMBPayload(new Object[] { ModelCommand.PROGRESS.getValue(),
480                             this.connection.getUniqueClientID(), this.progress });
481             if (eventName.length() == 0)
482             {
483                 this.controllersEvent.signalEvent(TEventEntry.EK_NORMAL_EVENT, payload.getBuffer());
484             }
485             else
486             {
487                 this.connection.signalEvent(eventName, TEventEntry.EK_NORMAL_EVENT, payload, false);
488             }
489         }
490     }
491 
492     /**
493      * Report a new progress value.
494      * @param currentProgress int; the new progress value
495      * @throws IMBException
496      */
497     public void signalModelProgress(int currentProgress) throws IMBException
498     {
499         this.controllersEvent.signalEvent(
500                 TEventEntry.EK_NORMAL_EVENT,
501                 ObjectArrayToIMB
502                         .objectArrayToIMBPayload(
503                                 new Object[] { ModelCommand.PROGRESS.getValue(), this.connection.getUniqueClientID(),
504                                         currentProgress }).getBuffer());
505         this.progress = currentProgress;
506     }
507 
508     /**
509      * Inform a federation about a state change.
510      * @param newState ModelState; the new state
511      * @param federation String; the federation
512      * @throws IMBException ...
513      */
514     public void signalModelState(final ModelState newState, final String federation) throws IMBException
515     {
516         ChangeEvent modelChangeEvent = new ChangeEvent(this.connection.getUniqueClientID(), newState, federation);
517         this.controllersEvent.signalEvent(
518                 TEventEntry.EK_NORMAL_EVENT,
519                 ObjectArrayToIMB.objectArrayToIMBPayload(
520                         new Object[] { ModelCommand.MODEL.getValue(), TEventEntry.ACTION_CHANGE, modelChangeEvent })
521                         .getBuffer());
522         System.out.println("New model state " + newState + " on " + federation);
523     }
524 
525     /**
526      * Inform our federation about a state change.
527      * @param newState ModelState; the new state
528      * @throws IMBException ...
529      */
530     public void signalModelState(final ModelState newState) throws IMBException
531     {
532         signalModelState(newState, this.connection.getFederation());
533     }
534 
535     /**
536      * Retrieve the remote port number.
537      * @return int; the remote port number
538      */
539     public int getRemotePort()
540     {
541         return this.remotePort;
542     }
543 
544     /**
545      * Retrieve the remote host name.
546      * @return String; the name of the remote host
547      */
548     public String getRemoteHost()
549     {
550         return this.remoteHost;
551     }
552 
553 }