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