View Javadoc
1   package org.opentrafficsim.aimsun;
2   
3   import java.awt.Dimension;
4   import java.io.ByteArrayInputStream;
5   import java.io.IOException;
6   import java.io.InputStream;
7   import java.io.OutputStream;
8   import java.io.PrintWriter;
9   import java.net.ServerSocket;
10  import java.net.Socket;
11  import java.net.URISyntaxException;
12  import java.nio.charset.StandardCharsets;
13  import java.rmi.RemoteException;
14  
15  import javax.naming.NamingException;
16  import javax.xml.bind.JAXBException;
17  import javax.xml.parsers.ParserConfigurationException;
18  
19  import org.djunits.unit.DurationUnit;
20  import org.djunits.unit.TimeUnit;
21  import org.djunits.value.ValueException;
22  import org.djunits.value.vdouble.scalar.Duration;
23  import org.djunits.value.vdouble.scalar.Length;
24  import org.djunits.value.vdouble.scalar.Time;
25  import org.djutils.logger.LogCategory;
26  import org.opentrafficsim.aimsun.proto.AimsunControlProtoBuf;
27  import org.opentrafficsim.aimsun.proto.AimsunControlProtoBuf.GTUPositions;
28  import org.opentrafficsim.base.parameters.ParameterException;
29  import org.opentrafficsim.core.animation.gtu.colorer.DefaultSwitchableGTUColorer;
30  import org.opentrafficsim.core.dsol.AbstractOTSModel;
31  import org.opentrafficsim.core.dsol.OTSAnimator;
32  import org.opentrafficsim.core.dsol.OTSModelInterface;
33  import org.opentrafficsim.core.dsol.OTSSimulatorInterface;
34  import org.opentrafficsim.core.geometry.OTSGeometryException;
35  import org.opentrafficsim.core.gtu.GTU;
36  import org.opentrafficsim.core.gtu.GTUException;
37  import org.opentrafficsim.core.gtu.GTUType;
38  import org.opentrafficsim.core.gtu.plan.operational.OperationalPlanException;
39  import org.opentrafficsim.core.network.NetworkException;
40  import org.opentrafficsim.core.network.OTSNetwork;
41  import org.opentrafficsim.draw.core.OTSDrawingException;
42  import org.opentrafficsim.draw.factory.DefaultAnimationFactory;
43  import org.opentrafficsim.road.network.OTSRoadNetwork;
44  import org.opentrafficsim.road.network.factory.xml.XmlParserException;
45  import org.opentrafficsim.road.network.factory.xml.parser.XmlNetworkLaneParser;
46  import org.opentrafficsim.road.network.lane.conflict.ConflictBuilder;
47  import org.opentrafficsim.swing.gui.OTSAnimationPanel;
48  import org.opentrafficsim.swing.gui.OTSSimulationApplication;
49  import org.opentrafficsim.swing.gui.OTSSwingApplication;
50  import org.pmw.tinylog.Level;
51  import org.xml.sax.SAXException;
52  
53  import nl.tudelft.simulation.dsol.SimRuntimeException;
54  import nl.tudelft.simulation.dsol.logger.SimLogger;
55  import nl.tudelft.simulation.dsol.simulators.DEVSRealTimeClock;
56  import nl.tudelft.simulation.dsol.simulators.SimulatorInterface;
57  import nl.tudelft.simulation.event.EventInterface;
58  import nl.tudelft.simulation.event.EventListenerInterface;
59  import nl.tudelft.simulation.language.d3.DirectedPoint;
60  
61  /**
62   * <p>
63   * Copyright (c) 2013-2019 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
64   * BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
65   * <p>
66   * @version $Revision$, $LastChangedDate$, by $Author$, initial version Apr 18, 2017 <br>
67   * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
68   * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
69   * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
70   */
71  public class AimsunControl
72  {
73      /** Currently active Aimsun model. */
74      private AimsunModel model = null;
75  
76      /**
77       * Program entry point.
78       * @param args String[]; the command line arguments
79       * @throws OTSGeometryException on error
80       * @throws NetworkException on error
81       * @throws NamingException on error
82       * @throws ValueException on error
83       * @throws SimRuntimeException on error
84       * @throws ParameterException on error
85       */
86      public static void main(final String[] args) throws NetworkException, OTSGeometryException, NamingException, ValueException,
87              ParameterException, SimRuntimeException
88      {
89          SimLogger.setAllLogLevel(Level.WARNING);
90          SimLogger.setLogCategories(LogCategory.ALL);
91  
92          String ip = null;
93          Integer port = null;
94  
95          for (String arg : args)
96          {
97              int equalsPos = arg.indexOf("=");
98              if (equalsPos < 0)
99              {
100                 System.err.println("Unhandled argument \"" + arg + "\"");
101             }
102             String key = arg.substring(0, equalsPos);
103             String value = arg.substring(equalsPos + 1);
104             switch (key.toUpperCase())
105             {
106                 case "IP":
107                     ip = value;
108                     break;
109                 case "PORT":
110                     try
111                     {
112                         port = Integer.parseInt(value);
113                     }
114                     catch (NumberFormatException exception)
115                     {
116                         System.err.println("Bad port number \"" + value + "\"");
117                         System.exit(1);
118                     }
119                     break;
120                 default:
121                     System.err.println("Unhandled argument \"" + arg + "\"");
122             }
123         }
124         if (null == ip || null == port)
125         {
126             System.err.println("Missing required argument(s) ip=<ip-number_or_hostname> port=<port-number>");
127             System.exit(1);
128         }
129         try
130         {
131             System.out.println("Creating server socket for port " + port);
132             ServerSocket serverSocket = new ServerSocket(port);
133             serverSocket.setReuseAddress(true); // Ensure we can be restarted without the normal 2 minute delay
134             System.out.println("Waiting for client to connect");
135             Socket clientSocket = serverSocket.accept();
136             System.out.println("Client connected; closing server socket");
137             serverSocket.close(); // don't accept any other connections
138             System.out.println("Socket time out is " + clientSocket.getSoTimeout());
139             clientSocket.setSoTimeout(0);
140             System.out.println("Constructing animation/simulation");
141             AimsunControl aimsunControl = new AimsunControl();
142             aimsunControl.commandLoop(clientSocket);
143         }
144         catch (IOException exception)
145         {
146             exception.printStackTrace();
147         }
148         System.exit(0);
149     }
150 
151     /** Shared between sendGTUPositionsToAimsun and the commandLoop methods. */
152     private Time simulateUntil = null;
153 
154     /**
155      * Construct a GTU positions message, clear the simulateUntil value, transmit all GTU positions to Aimsun and wait for the
156      * simulateUntil value to be set again.
157      * @param outputStream OutputStream;
158      */
159     protected void sendGTUPositionsToAimsun(final OutputStream outputStream)
160     {
161         OTSSimulatorInterface simulator = this.model.getSimulator();
162         System.out.println("Simulator has stopped at time " + simulator.getSimulatorTime());
163         Time stopTime = simulator.getSimulatorTime();
164         AimsunControlProtoBuf.GTUPositions.Builder builder = AimsunControlProtoBuf.GTUPositions.newBuilder();
165         for (GTU gtu : this.model.getNetwork().getGTUs())
166         {
167             AimsunControlProtoBuf.GTUPositions.GTUPosition.Builder gpb =
168                     AimsunControlProtoBuf.GTUPositions.GTUPosition.newBuilder();
169             gpb.setGtuId(gtu.getId());
170             DirectedPoint dp;
171             try
172             {
173                 dp = gtu.getOperationalPlan().getLocation(stopTime);
174                 gpb.setX(dp.x);
175                 gpb.setY(dp.y);
176                 gpb.setZ(dp.z);
177                 gpb.setAngle(dp.getRotZ());
178                 gpb.setLength(gtu.getLength().si);
179                 gpb.setWidth(gtu.getWidth().si);
180                 gpb.setGtuTypeId(Integer.parseInt(gtu.getGTUType().getId().split("\\.")[1]));
181                 gpb.setSpeed(gtu.getSpeed().si);
182                 builder.addGtuPos(gpb.build());
183             }
184             catch (OperationalPlanException exception)
185             {
186                 exception.printStackTrace();
187             }
188         }
189         builder.setStatus("OK");
190         GTUPositions gtuPositions = builder.build();
191         AimsunControlProtoBuf.OTSMessage.Builder resultBuilder = AimsunControlProtoBuf.OTSMessage.newBuilder();
192         resultBuilder.setGtuPositions(gtuPositions);
193         AimsunControlProtoBuf.OTSMessage result = resultBuilder.build();
194         this.simulateUntil = null;
195         try
196         {
197             transmitMessage(result, outputStream);
198         }
199         catch (IOException exception)
200         {
201             exception.printStackTrace();
202         }
203         System.out.println("Simulator waiting for new simulateUntil value");
204         while (this.simulateUntil == null)
205         {
206             try
207             {
208                 Thread.sleep(1);
209             }
210             catch (InterruptedException exception)
211             {
212                 // exception.printStackTrace();
213             }
214         }
215         try
216         {
217             simulator.scheduleEventAbs(this.simulateUntil, this, this, "sendGTUPositionsToAimsun",
218                     new Object[] { outputStream });
219         }
220         catch (SimRuntimeException exception)
221         {
222             exception.printStackTrace();
223         }
224         System.out.println("Simulator resuming");
225     }
226 
227     /**
228      * Process incoming commands.
229      * @param socket Socket; the communications channel to Aimsun
230      * @throws IOException when communication with Aimsun fails
231      * @throws OTSGeometryException on error
232      * @throws NetworkException on error
233      * @throws NamingException on error
234      * @throws ValueException on error
235      * @throws SimRuntimeException on error
236      * @throws ParameterException on error
237      */
238     private void commandLoop(final Socket socket) throws IOException, NetworkException, OTSGeometryException, NamingException,
239             ValueException, ParameterException, SimRuntimeException
240     {
241         System.out.println("Entering command loop");
242         InputStream inputStream = socket.getInputStream();
243         OutputStream outputStream = socket.getOutputStream();
244         String error = null;
245         boolean simulatorStarted = false;
246         while (null == error)
247         {
248             try
249             {
250                 byte[] sizeBytes = new byte[4];
251                 fillBuffer(inputStream, sizeBytes);
252                 int size = ((sizeBytes[0] & 0xff) << 24) + ((sizeBytes[1] & 0xff) << 16) + ((sizeBytes[2] & 0xff) << 8)
253                         + (sizeBytes[3] & 0xff);
254                 System.out.println("expecting message of " + size + " bytes");
255                 byte[] buffer = new byte[size];
256                 fillBuffer(inputStream, buffer);
257                 AimsunControlProtoBuf.OTSMessage message = AimsunControlProtoBuf.OTSMessage.parseFrom(buffer);
258 
259                 if (null == message)
260                 {
261                     System.out.println("Connection terminated; exiting");
262                     break;
263                 }
264                 switch (message.getMsgCase())
265                 {
266                     case CREATESIMULATION:
267                         System.out.println("Received CREATESIMULATION message");
268                         AimsunControlProtoBuf.CreateSimulation createSimulation = message.getCreateSimulation();
269                         String networkXML = createSimulation.getNetworkXML();
270                         try (PrintWriter pw = new PrintWriter("c:/Temp/AimsunOtsNetwork.xml"))
271                         {
272                             pw.print(networkXML);
273                         }
274                         Duration runDuration = new Duration(3600, DurationUnit.SECOND);
275                         System.out.println("runDuration " + runDuration);
276                         Duration warmupDuration = new Duration(0, DurationUnit.SECOND);
277                         try
278                         {
279                             OTSAnimator animator = new OTSAnimator();
280                             this.model = new AimsunModel(animator, "Aimsun generated model", "Aimsun model", networkXML);
281                             animator.initialize(Time.ZERO, warmupDuration, runDuration, this.model);
282                             OTSAnimationPanel animationPanel =
283                                     new OTSAnimationPanel(this.model.getNetwork().getExtent(), new Dimension(800, 600),
284                                             animator, this.model, OTSSwingApplication.DEFAULT_COLORER, this.model.getNetwork());
285                             DefaultAnimationFactory.animateXmlNetwork(this.model.getNetwork(), animator,
286                                     new DefaultSwitchableGTUColorer());
287                             new AimsunSwingApplication(this.model, animationPanel);
288                             animator.setSpeedFactor(Double.MAX_VALUE, true);
289                             animator.setSpeedFactor(1000.0, true);
290                         }
291                         catch (SimRuntimeException | NamingException | OTSDrawingException exception1)
292                         {
293                             exception1.printStackTrace();
294                             // Stop the simulation
295                             error = "XML ERROR";
296                         }
297                         break;
298 
299                     case SIMULATEUNTIL:
300                     {
301                         System.out.println("Received SIMULATEUNTIL message");
302                         AimsunControlProtoBuf.SimulateUntil simulateUntilThing = message.getSimulateUntil();
303                         Time stopTime = new Time(simulateUntilThing.getTime(), TimeUnit.BASE_SECOND);
304                         System.out.println("Simulate until " + stopTime + " ");
305                         OTSSimulatorInterface simulator = this.model.getSimulator();
306                         if (!simulatorStarted)
307                         {
308                             simulatorStarted = true;
309                             simulator.scheduleEventAbs(stopTime, this, this, "sendGTUPositionsToAimsun",
310                                     new Object[] { outputStream });
311                             System.out.println("Starting simulator");
312                             this.simulateUntil = stopTime;
313                             simulator.start();
314                         }
315                         else if (!simulator.isRunning())
316                         {
317                             // Whoops: simulator has stopped
318                             error = "HMM Simulator stopped";
319                         }
320                         else
321                         {
322                             System.out.println("Resuming simulator");
323                             this.simulateUntil = stopTime;
324                         }
325                         break;
326                     }
327 
328                     case GTUPOSITIONS:
329                         System.out.println("Received GTUPOSITIONS message SHOULD NOT HAPPEN");
330                         socket.close();
331                         return;
332 
333                     case MSG_NOT_SET:
334                         System.out.println("Received MSG_NOT_SET message SHOULD NOT HAPPEN");
335                         socket.close();
336                         return;
337 
338                     default:
339                         System.out.println("Received unknown message SHOULD NOT HAPPEN");
340                         socket.close();
341                         break;
342                 }
343             }
344             catch (IOException exception)
345             {
346                 exception.printStackTrace();
347                 break;
348             }
349         }
350     }
351 
352     /**
353      * Transmit a message
354      * @param message AimsunControlProtoBuf.OTSMessage; the message
355      * @param outputStream OutputStream; the output stream
356      * @throws IOException when transmission fails
357      */
358     private void transmitMessage(final AimsunControlProtoBuf.OTSMessage message, final OutputStream outputStream)
359             throws IOException
360     {
361         int size = message.getSerializedSize();
362         System.out.print("Transmitting " + message.getGtuPositions().getGtuPosCount() + " GTU positions and status \""
363                 + message.getGtuPositions().getStatus() + "\" encoded in " + size + " bytes ... ");
364         byte[] sizeBytes = new byte[4];
365         sizeBytes[0] = (byte) ((size >> 24) & 0xff);
366         sizeBytes[1] = (byte) ((size >> 16) & 0xff);
367         sizeBytes[2] = (byte) ((size >> 8) & 0xff);
368         sizeBytes[3] = (byte) (size & 0xff);
369         outputStream.write(sizeBytes);
370         byte[] buffer = new byte[size];
371         buffer = message.toByteArray();
372         outputStream.write(buffer);
373         System.out.println("Message sent");
374     }
375 
376     /**
377      * Fill a buffer from a stream; retry until the buffer is entirely filled.
378      * @param in InputStream; the input stream for the data
379      * @param buffer byte[]; the buffer
380      * @throws IOException when it is not possible to fill the entire buffer
381      */
382     static void fillBuffer(final InputStream in, final byte[] buffer) throws IOException
383     {
384         System.out.print("Need to read " + buffer.length + " bytes ... ");
385         int offset = 0;
386         while (true)
387         {
388             int bytesRead = in.read(buffer, offset, buffer.length - offset);
389             if (-1 == bytesRead)
390             {
391                 break;
392             }
393             offset += bytesRead;
394             if (buffer.length == offset)
395             {
396                 System.out.println("got all " + buffer.length + " requested bytes");
397                 break;
398             }
399             if (buffer.length < offset)
400             {
401                 System.out.println("Oops: Got more than " + buffer.length + " requested bytes");
402                 break;
403             }
404             System.out.print("now got " + offset + " bytes; need to read " + (buffer.length - offset) + " more bytes ... ");
405         }
406         if (offset != buffer.length)
407         {
408             throw new IOException("Got only " + offset + " of expected " + buffer.length + " bytes");
409         }
410     }
411 
412     /**
413      * The application.
414      */
415     class AimsunSwingApplication extends OTSSimulationApplication<OTSModelInterface>
416     {
417         /** */
418         private static final long serialVersionUID = 1L;
419 
420         /**
421          * @param model OTSModelInterface; the model
422          * @param panel OTSAnimationPanel; the panel of the main screen
423          * @throws OTSDrawingException on animation error
424          */
425         public AimsunSwingApplication(final OTSModelInterface model, final OTSAnimationPanel panel) throws OTSDrawingException
426         {
427             super(model, panel);
428         }
429     }
430 
431     /**
432      * The Model.
433      */
434     class AimsunModel extends AbstractOTSModel implements EventListenerInterface
435     {
436         /** */
437         private static final long serialVersionUID = 20170419L;
438 
439         /** The network. */
440         private OTSRoadNetwork network;
441 
442         /** The XML. */
443         private final String xml;
444 
445         /**
446          * @param simulator OTSSimulatorInterface; the simulator
447          * @param shortName String; the model name
448          * @param description String; the model description
449          * @param xml String; the XML description of the simulation model
450          */
451         public AimsunModel(OTSSimulatorInterface simulator, String shortName, String description, final String xml)
452         {
453             super(simulator, shortName, description);
454             this.xml = xml;
455         }
456 
457         /** {@inheritDoc} */
458         @Override
459         public void notify(EventInterface event) throws RemoteException
460         {
461             System.out.println("Received event " + event);
462         }
463 
464         /** {@inheritDoc} */
465         @Override
466         public void constructModel() throws SimRuntimeException
467         {
468             try
469             {
470                 this.simulator.addListener(this, DEVSRealTimeClock.CHANGE_SPEED_FACTOR_EVENT);
471                 this.simulator.addListener(this, SimulatorInterface.TIME_CHANGED_EVENT);
472             }
473             catch (RemoteException exception1)
474             {
475                 exception1.printStackTrace();
476             }
477             this.network = new OTSRoadNetwork(getShortName(), true);
478             try
479             {
480                 XmlNetworkLaneParser.build(new ByteArrayInputStream(this.xml.getBytes(StandardCharsets.UTF_8)),
481                         this.network, getSimulator());
482                 ConflictBuilder.buildConflicts(this.network, this.network.getGtuType(GTUType.DEFAULTS.VEHICLE), getSimulator(),
483                         new ConflictBuilder.FixedWidthGenerator(Length.createSI(2.0)));
484             }
485             catch (NetworkException | OTSGeometryException | JAXBException | URISyntaxException | XmlParserException
486                     | SAXException | ParserConfigurationException | GTUException exception)
487             {
488                 exception.printStackTrace();
489                 // Abusing the SimRuntimeException to propagate the message to the main method (the problem could actually be a
490                 // parsing problem)
491                 throw new SimRuntimeException(exception);
492             }
493         }
494 
495         /** {@inheritDoc} */
496         @Override
497         public OTSNetwork getNetwork()
498         {
499             return this.network;
500         }
501 
502     }
503 
504 }