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