View Javadoc
1   package org.opentrafficsim.demo.web;
2   
3   import java.io.IOException;
4   import java.net.URL;
5   
6   import javax.naming.NamingException;
7   
8   import org.djunits.unit.DurationUnit;
9   import org.djunits.value.vdouble.scalar.Duration;
10  import org.djunits.value.vdouble.scalar.Time;
11  import org.djutils.cli.Checkable;
12  import org.djutils.cli.CliUtil;
13  import org.djutils.io.URLResource;
14  import org.djutils.serialization.SerializationException;
15  import org.opentrafficsim.core.dsol.OTSAnimator;
16  import org.opentrafficsim.core.dsol.OTSModelInterface;
17  import org.opentrafficsim.core.dsol.OTSReplication;
18  import org.opentrafficsim.core.dsol.OTSSimulatorInterface;
19  import org.opentrafficsim.demo.CircularRoadModel;
20  import org.opentrafficsim.demo.CrossingTrafficLightsModel;
21  import org.opentrafficsim.demo.NetworksModel;
22  import org.opentrafficsim.demo.ShortMerge;
23  import org.opentrafficsim.demo.StraightModel;
24  import org.opentrafficsim.demo.conflict.BusStreetDemo;
25  import org.opentrafficsim.demo.conflict.TJunctionDemo;
26  import org.opentrafficsim.demo.conflict.TurboRoundaboutDemo;
27  import org.opentrafficsim.demo.trafficcontrol.TrafCODDemo1;
28  import org.opentrafficsim.demo.trafficcontrol.TrafCODDemo2;
29  import org.sim0mq.Sim0MQException;
30  import org.sim0mq.message.MessageUtil;
31  import org.sim0mq.message.Sim0MQMessage;
32  import org.sim0mq.message.federatestarter.FS1RequestStatusMessage;
33  import org.sim0mq.message.federationmanager.FM2SimRunControlMessage;
34  import org.sim0mq.message.federationmanager.FM3SetParameterMessage;
35  import org.sim0mq.message.federationmanager.FM4SimStartMessage;
36  import org.sim0mq.message.federationmanager.FM5RequestStatus;
37  import org.sim0mq.message.federationmanager.FM6RequestStatisticsMessage;
38  import org.sim0mq.message.modelcontroller.MC1StatusMessage;
39  import org.sim0mq.message.modelcontroller.MC2AckNakMessage;
40  import org.sim0mq.message.modelcontroller.MC3StatisticsMessage;
41  import org.sim0mq.message.modelcontroller.MC4StatisticsErrorMessage;
42  import org.zeromq.SocketType;
43  import org.zeromq.ZContext;
44  import org.zeromq.ZMQ;
45  
46  import nl.tudelft.simulation.dsol.SimRuntimeException;
47  import nl.tudelft.simulation.dsol.experiment.ReplicationMode;
48  import picocli.CommandLine.Option;
49  
50  /**
51   * <p>
52   * Copyright (c) 2002-2014 Delft University of Technology, Jaffalaan 5, 2628 BX Delft, the Netherlands. All rights reserved.
53   * <p>
54   * See for project information <a href="http://www.simulation.tudelft.nl/"> www.simulation.tudelft.nl</a>.
55   * <p>
56   * The DSOL project is distributed under a BSD-style license.<br>
57   * @version Aug 15, 2014 <br>
58   * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
59   */
60  public class SuperDemoWebApplication implements Checkable
61  {
62      /** */
63      private OTSSimulatorInterface simulator;
64  
65      /** */
66      private OTSModelInterface model;
67  
68      /** the socket. */
69      private ZMQ.Socket fsSocket;
70  
71      /** the context. */
72      private ZContext fsContext;
73  
74      /** federation run id. */
75      private Object federationRunId;
76  
77      /** modelId unique Id of the model that is used as the sender/receiver when communicating. */
78      private String modelId;
79  
80      /** runtime. */
81      private Duration runDuration;
82  
83      /** warmup. */
84      private Duration warmupDuration;
85  
86      /** message count. */
87      private long messageCount = 0;
88  
89      /** home page for the web server. */
90      @SuppressWarnings("checkstyle:visibilitymodifier")
91      @Option(names = {"-m", "--modelId"}, description = "Id of the model to run", required = true)
92      String homePage;
93  
94      /** internet port for the web server. */
95      @SuppressWarnings("checkstyle:visibilitymodifier")
96      @Option(names = {"-p", "--port"}, description = "Internet port to use", defaultValue = "8081")
97      int port;
98  
99      /** {@inheritDoc} */
100     @Override
101     public void check() throws Exception
102     {
103         if (this.port < 1 || this.port > 65535)
104         {
105             throw new Exception("Port number should be between 1 and 65535");
106         }
107     }
108 
109     /**
110      * Construct a console application.
111      * @throws SimRuntimeException on error
112      * @throws NamingException on error
113      * @throws Sim0MQException on error
114      * @throws SerializationException on serialization problem
115      * @throws IOException when TRAFCOD file cannot be found
116      */
117     protected void init() throws SimRuntimeException, NamingException, Sim0MQException, SerializationException, IOException
118     {
119         this.simulator = new OTSAnimator("SuperDemoWebApplication");
120         this.modelId = this.modelId.trim();
121         if (this.modelId.toLowerCase().contains("circularroad"))
122         {
123             this.model = new CircularRoadModel(this.simulator);
124         }
125         else if (this.modelId.toLowerCase().contains("straight"))
126         {
127             this.model = new StraightModel(this.simulator);
128         }
129         else if (this.modelId.toLowerCase().contains("shortmerge"))
130         {
131             this.model = new ShortMerge.ShortMergeModel(this.simulator);
132         }
133         else if (this.modelId.toLowerCase().contains("networksdemo"))
134         {
135             this.model = new NetworksModel(this.simulator);
136         }
137         else if (this.modelId.toLowerCase().contains("crossingtrafficlights"))
138         {
139             this.model = new CrossingTrafficLightsModel(this.simulator);
140         }
141         else if (this.modelId.toLowerCase().contains("trafcoddemosimple"))
142         {
143             URL url = URLResource.getResource("/TrafCODDemo1/TrafCODDemo1.xml");
144             String xml = TrafCODDemo2.readStringFromURL(url);
145             this.model = new TrafCODDemo1.TrafCODModel(this.simulator, "TrafCODDemo1", "TrafCODDemo1", xml);
146         }
147         else if (this.modelId.toLowerCase().contains("trafcoddemocomplex"))
148         {
149             URL url = URLResource.getResource("/TrafCODDemo2/TrafCODDemo2.xml");
150             String xml = TrafCODDemo2.readStringFromURL(url);
151             this.model = new TrafCODDemo2.TrafCODModel(this.simulator, "TrafCODDemo2", "TrafCODDemo2", xml);
152         }
153         else if (this.modelId.toLowerCase().contains("tjunction"))
154         {
155             this.model = new TJunctionDemo.TJunctionModel(this.simulator);
156         }
157         else if (this.modelId.toLowerCase().contains("busstreet"))
158         {
159             this.model = new BusStreetDemo.BusStreetModel(this.simulator);
160         }
161         else if (this.modelId.toLowerCase().contains("turboroundabout"))
162         {
163             this.model = new TurboRoundaboutDemo.TurboRoundaboutModel(this.simulator);
164         }
165 
166         if (this.model == null)
167         {
168             System.err.println("Could not find model " + this.modelId);
169         }
170         else
171         {
172             startListener();
173         }
174     }
175 
176     /**
177      * Start listening on a port.
178      * @throws Sim0MQException on error
179      * @throws SerializationException on serialization problem
180      */
181     protected void startListener() throws Sim0MQException, SerializationException
182     {
183         this.fsContext = new ZContext(1);
184 
185         this.fsSocket = this.fsContext.createSocket(SocketType.ROUTER);
186         this.fsSocket.bind("tcp://*:" + this.port);
187 
188         System.out.println("Model started. Listening at port: " + this.port);
189         System.out.flush();
190 
191         while (!Thread.currentThread().isInterrupted())
192         {
193             // Wait for next request from the client -- first the identity (String) and the delimiter (#0)
194             String identity = this.fsSocket.recvStr();
195             this.fsSocket.recvStr();
196 
197             byte[] request = this.fsSocket.recv(0);
198             System.out.println(MessageUtil.printBytes(request));
199             Object[] fields = Sim0MQMessage.decodeToArray(request);
200             Object receiverId = fields[4];
201             Object messageTypeId = fields[5];
202 
203             System.out.println("Received " + Sim0MQMessage.print(fields));
204             System.out.flush();
205 
206             if (receiverId.equals(this.modelId))
207             {
208                 switch (messageTypeId.toString())
209                 {
210                     case "FS.1":
211                         processRequestStatus(identity, new FS1RequestStatusMessage(fields));
212                         break;
213 
214                     case "FM.5":
215                         processRequestStatus(identity, new FM5RequestStatus(fields));
216                         break;
217 
218                     case "FM.2":
219                         processSimRunControl(identity, new FM2SimRunControlMessage(fields));
220                         break;
221 
222                     case "FM.3":
223                         processSetParameter(identity, new FM3SetParameterMessage(fields));
224                         break;
225 
226                     case "FM.4":
227                         processSimStart(identity, new FM4SimStartMessage(fields));
228                         break;
229 
230                     case "FM.6":
231                         processRequestStatistics(identity, new FM6RequestStatisticsMessage(fields));
232                         break;
233 
234                     case "FS.3":
235                         processKillFederate();
236                         break;
237 
238                     default:
239                         // wrong message
240                         System.err.println("Received unknown message -- not processed: " + messageTypeId);
241                 }
242             }
243             else
244             {
245                 // wrong receiver
246                 System.err.println(
247                         "Received message not intended for " + this.modelId + " but for " + receiverId + " -- not processed: ");
248             }
249         }
250     }
251 
252     /**
253      * Process FS.1 or FM.5 message and send MC.1 message back.
254      * @param identity reply id for REQ-ROUTER pattern
255      * @param message the message (0 payload fields)
256      * @throws Sim0MQException on error
257      * @throws SerializationException on serialization problem
258      */
259     private void processRequestStatus(final String identity, final Sim0MQMessage message)
260             throws Sim0MQException, SerializationException
261     {
262         if (this.federationRunId == null)
263         {
264             this.federationRunId = message.getFederationId();
265         }
266         String status = "started";
267         if (this.simulator.isStartingOrRunning())
268         {
269             status = "running";
270         }
271         else if (this.simulator.getSimulatorTime() != null && this.simulator.getReplication() != null
272                 && this.simulator.getReplication().getTreatment() != null)
273         {
274             if (this.simulator.getSimulatorTime().ge(this.simulator.getReplication().getTreatment().getEndTime()))
275             {
276                 status = "ended";
277             }
278             else
279             {
280                 status = "error";
281             }
282         }
283         this.fsSocket.sendMore(identity);
284         this.fsSocket.sendMore("");
285         byte[] mc1Message = new MC1StatusMessage(this.federationRunId, this.modelId, message.getSenderId(), ++this.messageCount,
286                 message.getMessageId(), status, "").createByteArray();
287         this.fsSocket.send(mc1Message, 0);
288 
289         System.out.println("Sent MC.1");
290         System.out.flush();
291     }
292 
293     /**
294      * Process FM.2 message and send MC.2 message back.
295      * @param identity reply id for REQ-ROUTER pattern
296      * @param message the FM.2 message
297      * @throws Sim0MQException on error
298      * @throws SerializationException on serialization problem
299      */
300     private void processSimRunControl(final String identity, final FM2SimRunControlMessage message)
301             throws Sim0MQException, SerializationException
302     {
303         boolean status = true;
304         String error = "";
305         try
306         {
307             Object runDurationField = message.getRunDuration();
308             if (runDurationField instanceof Number)
309             {
310                 this.runDuration = new Duration(((Number) runDurationField).doubleValue(), DurationUnit.SI);
311             }
312             else if (runDurationField instanceof Duration)
313             {
314                 this.runDuration = (Duration) runDurationField;
315             }
316             else
317             {
318                 throw new Sim0MQException("runTimeField " + runDurationField + " neither Number nor Duration");
319             }
320 
321             Object warmupDurationField = message.getWarmupDuration();
322             if (warmupDurationField instanceof Number)
323             {
324                 this.warmupDuration = new Duration(((Number) warmupDurationField).doubleValue(), DurationUnit.SI);
325             }
326             else if (warmupDurationField instanceof Duration)
327             {
328                 this.warmupDuration = (Duration) warmupDurationField;
329             }
330             else
331             {
332                 throw new Sim0MQException("warmupField " + warmupDurationField + " neither Number nor Duration");
333             }
334         }
335         catch (Exception e)
336         {
337             status = false;
338             error = e.getMessage();
339         }
340         byte[] mc2Message = new MC2AckNakMessage(this.federationRunId, this.modelId, message.getSenderId(), ++this.messageCount,
341                 message.getMessageId(), status, error).createByteArray();
342         this.fsSocket.sendMore(identity);
343         this.fsSocket.sendMore("");
344         this.fsSocket.send(mc2Message, 0);
345 
346         System.out.println("Sent MC.2");
347         System.out.flush();
348     }
349 
350     /**
351      * Process FM.3 message and send MC.2 message back.
352      * @param identity reply id for REQ-ROUTER pattern
353      * @param message the FM3 message
354      * @throws Sim0MQException on error
355      * @throws SerializationException on serialization problem
356      */
357     private void processSetParameter(final String identity, final FM3SetParameterMessage message)
358             throws Sim0MQException, SerializationException
359     {
360         boolean status = true;
361         String error = "";
362         try
363         {
364             // TODO: change for InputParameter
365             /*-
366             String parameterName = message.getParameterName();
367             Object parameterValueField = message.getParameterValue();
368             
369             switch (parameterName)
370             {
371                 case "seed":
372                     this.model.seed = ((Number) parameterValueField).longValue();
373                     break;
374             
375                 case "iat":
376                     this.model.iat = ((Number) parameterValueField).doubleValue();
377                     break;
378             
379                 case "servicetime":
380                     this.model.serviceTime = ((Number) parameterValueField).doubleValue();
381                     break;
382             
383                 default:
384                     status = false;
385                     error = "Parameter " + parameterName + " unknown";
386                     break;
387             }
388             */
389         }
390         catch (Exception e)
391         {
392             status = false;
393             error = e.getMessage();
394         }
395 
396         byte[] mc2Message = new MC2AckNakMessage(this.federationRunId, this.modelId, message.getSenderId(), ++this.messageCount,
397                 message.getMessageId(), status, error).createByteArray();
398         this.fsSocket.sendMore(identity);
399         this.fsSocket.sendMore("");
400         this.fsSocket.send(mc2Message, 0);
401 
402         System.out.println("Sent MC.2");
403         System.out.flush();
404     }
405 
406     /**
407      * Process FM.4 message and send MC.2 message back.
408      * @param identity reply id for REQ-ROUTER pattern
409      * @param message the FM.4 message
410      * @throws Sim0MQException on error
411      * @throws SerializationException on serialization problem
412      */
413     private void processSimStart(final String identity, final FM4SimStartMessage message)
414             throws Sim0MQException, SerializationException
415     {
416         boolean status = true;
417         String error = "";
418         try
419         {
420             OTSReplication replication =
421                     OTSReplication.create("rep1", Time.ZERO, this.warmupDuration, this.runDuration, this.model);
422             this.simulator.initialize(replication, ReplicationMode.TERMINATING);
423             // TODO: different... this.simulator.scheduleEventAbs(100.0, this, this, "terminate", null);
424 
425             this.simulator.start();
426         }
427         catch (Exception e)
428         {
429             status = false;
430             error = e.getMessage();
431         }
432 
433         byte[] mc2Message = new MC2AckNakMessage(this.federationRunId, this.modelId, message.getSenderId(), ++this.messageCount,
434                 message.getMessageId(), status, error).createByteArray();
435         this.fsSocket.sendMore(identity);
436         this.fsSocket.sendMore("");
437         this.fsSocket.send(mc2Message, 0);
438 
439         System.out.println("Sent MC.2");
440         System.out.flush();
441     }
442 
443     /**
444      * Process FM.6 message and send MC.3 or MC.4 message back.
445      * @param identity reply id for REQ-ROUTER pattern
446      * @param message the FM.6 message
447      * @throws Sim0MQException on error
448      * @throws SerializationException on serialization problem
449      */
450     private void processRequestStatistics(final String identity, final FM6RequestStatisticsMessage message)
451             throws Sim0MQException, SerializationException
452     {
453         boolean ok = true;
454         String error = "";
455         String variableName = message.getVariableName();
456         double variableValue = Double.NaN;
457         try
458         {
459             // TODO: change for real outputs of the model.
460             /*-
461             switch (variableName)
462             {
463                 case "dN.average":
464                     variableValue = this.model.dN.getSampleMean();
465                     break;
466             
467                 case "uN.average":
468                     variableValue = this.model.uN.getSampleMean();
469                     break;
470             
471                 case "qN.max":
472                     variableValue = this.model.qN.getMax();
473                     break;
474             
475                 default:
476                     ok = false;
477                     error = "Parameter " + variableName + " unknown";
478                     break;
479             }
480             */
481         }
482         catch (Exception e)
483         {
484             ok = false;
485             error = e.getMessage();
486         }
487 
488         if (Double.isNaN(variableValue))
489         {
490             ok = false;
491             error = "Parameter " + variableName + " not set to a value";
492         }
493 
494         if (ok)
495         {
496             byte[] mc3Message = new MC3StatisticsMessage(this.federationRunId, this.modelId, message.getSenderId(),
497                     ++this.messageCount, variableName, variableValue).createByteArray();
498             this.fsSocket.sendMore(identity);
499             this.fsSocket.sendMore("");
500             this.fsSocket.send(mc3Message, 0);
501 
502             System.out.println("Sent MC.3");
503             System.out.flush();
504         }
505         else
506         {
507             byte[] mc4Message = new MC4StatisticsErrorMessage(this.federationRunId, this.modelId, message.getSenderId(),
508                     ++this.messageCount, variableName, error).createByteArray();
509             this.fsSocket.sendMore(identity);
510             this.fsSocket.sendMore("");
511             this.fsSocket.send(mc4Message, 0);
512 
513             System.out.println("Sent MC.4");
514             System.out.flush();
515         }
516     }
517 
518     /**
519      * Process FS.3 message.
520      */
521     private void processKillFederate()
522     {
523         this.fsSocket.close();
524         this.fsContext.destroy();
525         this.fsContext.close();
526         System.exit(0);
527     }
528 
529     /** stop the simulation. */
530     protected final void terminate()
531     {
532         // TODO: This probably goes away or is replaced by metadata
533         /*-
534         System.out.println("average queue length = " + this.model.qN.getSampleMean());
535         System.out.println("average queue wait   = " + this.model.dN.getSampleMean());
536         System.out.println("average utilization  = " + this.model.uN.getSampleMean());
537         */
538     }
539 
540     /**
541      * @param args contain e.g., port number, and a model to run: SuperDemoWebpplication --port=8080 -m TJunctionDemo.
542      * @throws SimRuntimeException on error
543      * @throws NamingException on error
544      * @throws Sim0MQException on error
545      * @throws SerializationException on serialization problem
546      * @throws IOException when TRAFCODDEMO file cannot be found
547      */
548     public static void main(final String[] args)
549             throws SimRuntimeException, NamingException, Sim0MQException, SerializationException, IOException
550     {
551         SuperDemoWebApplicationcation.html#SuperDemoWebApplication">SuperDemoWebApplication webApp = new SuperDemoWebApplication();
552         CliUtil.execute(webApp, args);
553         webApp.init();
554     }
555 
556 }