View Javadoc
1   package org.opentrafficsim.aimsun;
2   
3   import java.io.ByteArrayInputStream;
4   import java.io.IOException;
5   import java.io.InputStream;
6   import java.io.OutputStream;
7   import java.io.PrintWriter;
8   import java.net.ServerSocket;
9   import java.net.Socket;
10  import java.nio.charset.StandardCharsets;
11  import java.rmi.RemoteException;
12  import java.util.ArrayList;
13  
14  import javax.naming.NamingException;
15  import javax.xml.parsers.ParserConfigurationException;
16  
17  import org.djunits.unit.DurationUnit;
18  import org.djunits.unit.TimeUnit;
19  import org.djunits.value.ValueException;
20  import org.djunits.value.vdouble.scalar.Duration;
21  import org.djunits.value.vdouble.scalar.Time;
22  import org.opentrafficsim.aimsun.proto.AimsunControlProtoBuf;
23  import org.opentrafficsim.aimsun.proto.AimsunControlProtoBuf.GTUPositions;
24  import org.opentrafficsim.base.modelproperties.Property;
25  import org.opentrafficsim.base.modelproperties.PropertyException;
26  import org.opentrafficsim.base.parameters.ParameterException;
27  import org.opentrafficsim.core.dsol.OTSModelInterface;
28  import org.opentrafficsim.core.geometry.OTSGeometryException;
29  import org.opentrafficsim.core.gtu.GTU;
30  import org.opentrafficsim.core.gtu.GTUException;
31  import org.opentrafficsim.core.gtu.plan.operational.OperationalPlanException;
32  import org.opentrafficsim.core.network.NetworkException;
33  import org.opentrafficsim.core.network.OTSLink;
34  import org.opentrafficsim.core.network.OTSNetwork;
35  import org.opentrafficsim.core.network.OTSNode;
36  import org.opentrafficsim.road.animation.AnimationToggles;
37  import org.opentrafficsim.road.network.factory.xml.XmlNetworkLaneParser;
38  import org.opentrafficsim.road.network.lane.object.SpeedSign;
39  import org.opentrafficsim.simulationengine.AbstractWrappableAnimation;
40  import org.opentrafficsim.simulationengine.OTSSimulationException;
41  import org.opentrafficsim.simulationengine.SimpleAnimator;
42  import org.xml.sax.SAXException;
43  
44  import nl.tudelft.simulation.dsol.SimRuntimeException;
45  import nl.tudelft.simulation.dsol.simtime.SimTimeDoubleUnit;
46  import nl.tudelft.simulation.dsol.simulators.DEVSSimulator;
47  import nl.tudelft.simulation.dsol.simulators.DEVSSimulatorInterface;
48  import nl.tudelft.simulation.dsol.simulators.SimulatorInterface;
49  import nl.tudelft.simulation.event.EventInterface;
50  import nl.tudelft.simulation.event.EventListenerInterface;
51  import nl.tudelft.simulation.event.EventProducer;
52  import nl.tudelft.simulation.language.d3.DirectedPoint;
53  
54  /**
55   * <p>
56   * Copyright (c) 2013-2018 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
57   * BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
58   * <p>
59   * @version $Revision$, $LastChangedDate$, by $Author$, initial version Apr 18, 2017 <br>
60   * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
61   * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
62   * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
63   */
64  public class AimsunControl extends AbstractWrappableAnimation
65  {
66  
67      /** */
68      private static final long serialVersionUID = 20160418L;
69  
70      /** XML description of the network. */
71      private String networkXML = null;
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          String ip = null;
90          Integer port = null;
91  
92          for (String arg : args)
93          {
94              int equalsPos = arg.indexOf("=");
95              if (equalsPos < 0)
96              {
97                  System.err.println("Unhandled argument \"" + arg + "\"");
98              }
99              String key = arg.substring(0, equalsPos);
100             String value = arg.substring(equalsPos + 1);
101             switch (key.toUpperCase())
102             {
103                 case "IP":
104                     ip = value;
105                     break;
106                 case "PORT":
107                     try
108                     {
109                         port = Integer.parseInt(value);
110                     }
111                     catch (NumberFormatException exception)
112                     {
113                         System.err.println("Bad port number \"" + value + "\"");
114                         System.exit(1);
115                     }
116                     break;
117                 default:
118                     System.err.println("Unhandled argument \"" + arg + "\"");
119             }
120         }
121         if (null == ip || null == port)
122         {
123             System.err.println("Missing required argument(s) ip=<ip-number_or_hostname> port=<port-number>");
124             System.exit(1);
125         }
126         try
127         {
128             System.out.println("Creating server socket for port " + port);
129             ServerSocket serverSocket = new ServerSocket(port);
130             serverSocket.setReuseAddress(true); // Ensure we can be restarted without the normal delay
131             System.out.println("Waiting for client to connect");
132             Socket clientSocket = serverSocket.accept();
133             System.out.println("Client connected; closing server socket");
134             serverSocket.close(); // don't accept any other connections
135             System.out.println("Socket time out is " + clientSocket.getSoTimeout());
136             clientSocket.setSoTimeout(0);
137             System.out.println("Constructing animation/simulation");
138             AimsunControl aimsunControl = new AimsunControl();
139             aimsunControl.commandLoop(clientSocket);
140         }
141         catch (IOException exception)
142         {
143             exception.printStackTrace();
144         }
145         System.exit(0);
146     }
147 
148     /**
149      * Create a nice hex dump of a bunch of bytes.
150      * @param bytes byte[]; the bytes
151      * @return String; the hex dump
152      */
153     public static String hexDump(final byte[] bytes)
154     {
155         StringBuilder result = new StringBuilder();
156         int pos = 0;
157         for (byte b : bytes)
158         {
159             result.append(String.format("%02x", b));
160             if (pos % 16 == 0)
161             {
162                 result.append("\r\n");
163             }
164             else if (pos % 8 == 0)
165             {
166                 result.append("  ");
167             }
168             else
169             {
170                 result.append(" ");
171             }
172         }
173         return result.toString();
174     }
175 
176     /**
177      * Process incoming commands.
178      * @param socket Socket; the communications channel to Aimsun
179      * @throws IOException when communication with Aimsun fails
180      * @throws OTSGeometryException on error
181      * @throws NetworkException on error
182      * @throws NamingException on error
183      * @throws ValueException on error
184      * @throws SimRuntimeException on error
185      * @throws ParameterException on error
186      */
187     private void commandLoop(final Socket socket) throws IOException, NetworkException, OTSGeometryException, NamingException,
188             ValueException, ParameterException, SimRuntimeException
189     {
190         System.out.println("Entering command loop");
191         InputStream inputStream = socket.getInputStream();
192         OutputStream outputStream = socket.getOutputStream();
193         String error = null;
194         while (true)
195         {
196             // byte[] in = new byte[1];
197             // inputStream.read(in);
198             // System.out.println(String.format("Got byte %02x", in[0]));
199             try
200             {
201                 byte[] sizeBytes = new byte[4];
202                 // inputStream.read(sizeBytes);
203                 fillBuffer(inputStream, sizeBytes);
204                 int size = ((sizeBytes[0] & 0xff) << 24) + ((sizeBytes[1] & 0xff) << 16) + ((sizeBytes[2] & 0xff) << 8)
205                         + (sizeBytes[3] & 0xff);
206                 System.out.println("expecting message of " + size + " bytes");
207                 byte[] buffer = new byte[size];
208                 // inputStream.read(buffer);
209                 fillBuffer(inputStream, buffer);
210                 AimsunControlProtoBuf.OTSMessage message = AimsunControlProtoBuf.OTSMessage.parseFrom(buffer);
211 
212                 // AimsunControlProtoBuf.OTSMessage message = AimsunControlProtoBuf.OTSMessage.parseDelimitedFrom(inputStream);
213                 if (null == message)
214                 {
215                     System.out.println("Connection terminated; exiting");
216                     break;
217                 }
218                 switch (message.getMsgCase())
219                 {
220                     case CREATESIMULATION:
221                         System.out.println("Received CREATESIMULATION message");
222                         AimsunControlProtoBuf.CreateSimulation createSimulation = message.getCreateSimulation();
223                         this.networkXML = createSimulation.getNetworkXML();
224                         try (PrintWriter pw = new PrintWriter("d:/AimsunOtsNetwork.xml"))
225                         {
226                             pw.print(this.networkXML);
227                         }
228                         Duration runDuration = new Duration(createSimulation.getRunTime(), DurationUnit.SECOND);
229                         System.out.println("runDuration " + runDuration);
230                         Duration warmupDuration = new Duration(createSimulation.getWarmUpTime(), DurationUnit.SECOND);
231                         try
232                         {
233                             SimpleAnimator animator = buildAnimator(Time.ZERO, warmupDuration, runDuration,
234                                     new ArrayList<Property<?>>(), null, true);
235                             animator.setSpeedFactor(Double.MAX_VALUE, true);
236                         }
237                         catch (SimRuntimeException | NamingException | OTSSimulationException | PropertyException exception1)
238                         {
239                             exception1.printStackTrace();
240                             // Stop the simulation
241                             error = "XML ERROR";
242                         }
243                         break;
244 
245                     case SIMULATEUNTIL:
246                         System.out.println("Received SIMULATEUNTIL message");
247                         if (null == this.model)
248                         {
249                             System.err.println("No model active");
250                             socket.close();
251                             break;
252                         }
253                         AimsunControlProtoBuf.SimulateUntil simulateUntil = message.getSimulateUntil();
254                         Time stopTime = new Time(simulateUntil.getTime(), TimeUnit.BASE_SECOND);
255                         System.out.println("Simulate until " + stopTime);
256                         DEVSSimulator<Time, ?, ?> simulator = (DEVSSimulator<Time, ?, ?>) this.model.getSimulator();
257                         try
258                         {
259                             if (null != error)
260                             {
261                                 throw new SimRuntimeException(error);
262                             }
263                             simulator.runUpTo(stopTime);
264                             while (simulator.isRunning())
265                             {
266                                 try
267                                 {
268                                     Thread.sleep(10);
269                                 }
270                                 catch (InterruptedException ie)
271                                 {
272                                     ie = null; // ignore
273                                 }
274                             }
275                             AimsunControlProtoBuf.GTUPositions.Builder builder =
276                                     AimsunControlProtoBuf.GTUPositions.newBuilder();
277                             for (GTU gtu : this.model.getNetwork().getGTUs())
278                             {
279                                 AimsunControlProtoBuf.GTUPositions.GTUPosition.Builder gpb =
280                                         AimsunControlProtoBuf.GTUPositions.GTUPosition.newBuilder();
281                                 gpb.setGtuId(gtu.getId());
282                                 DirectedPoint dp = gtu.getOperationalPlan().getLocation(stopTime);
283                                 gpb.setX(dp.x);
284                                 gpb.setY(dp.y);
285                                 gpb.setZ(dp.z);
286                                 gpb.setAngle(dp.getRotZ());
287                                 gpb.setLength(gtu.getLength().si);
288                                 gpb.setWidth(gtu.getWidth().si);
289                                 gpb.setGtuTypeId(Integer.parseInt(gtu.getGTUType().getId().split("\\.")[1]));
290                                 gpb.setSpeed(gtu.getSpeed().si);
291                                 builder.addGtuPos(gpb.build());
292                             }
293                             builder.setStatus("OK");
294                             GTUPositions gtuPositions = builder.build();
295                             AimsunControlProtoBuf.OTSMessage.Builder resultBuilder =
296                                     AimsunControlProtoBuf.OTSMessage.newBuilder();
297                             resultBuilder.setGtuPositions(gtuPositions);
298                             AimsunControlProtoBuf.OTSMessage result = resultBuilder.build();
299                             transmitMessage(result, outputStream);
300                             /*
301                              * size = result.getSerializedSize(); System.out.print("Transmitting " +
302                              * this.model.getNetwork().getGTUs().size() + " GTU positions encoded in " + size + " bytes ... ");
303                              * sizeBytes[0] = (byte) ((size >> 24) & 0xff); sizeBytes[1] = (byte) ((size >> 16) & 0xff);
304                              * sizeBytes[2] = (byte) ((size >> 8) & 0xff); sizeBytes[3] = (byte) (size & 0xff);
305                              * outputStream.write(sizeBytes); buffer = new byte[size]; buffer = result.toByteArray();
306                              * outputStream.write(buffer); System.out.println("Done");
307                              */
308                         }
309                         catch (SimRuntimeException | OperationalPlanException exception)
310                         {
311                             if (null == error)
312                             {
313                                 error = "Error while handling SIMULATEUNTIL";
314                                 System.out.println(error);
315                                 exception.printStackTrace();
316                             }
317                             // Stop the simulation
318                             AimsunControlProtoBuf.GTUPositions.Builder builder =
319                                     AimsunControlProtoBuf.GTUPositions.newBuilder();
320                             builder.setStatus("FAILED (" + error + ")");
321                             AimsunControlProtoBuf.OTSMessage.Builder resultBuilder =
322                                     AimsunControlProtoBuf.OTSMessage.newBuilder();
323                             resultBuilder.setGtuPositions(builder);
324                             transmitMessage(resultBuilder.build(), outputStream);
325                         }
326                         break;
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("Done");
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     /** {@inheritDoc} */
413     @Override
414     public final String shortName()
415     {
416         return "AimsunControlledOTS";
417     }
418 
419     /** {@inheritDoc} */
420     @Override
421     public final String description()
422     {
423         return "Aimsun controlled OTS engine";
424     }
425 
426     /** {@inheritDoc} */
427     @Override
428     protected OTSModelInterface makeModel() throws OTSSimulationException
429     {
430         this.model = new AimsunModel();
431         return this.model;
432     }
433 
434     /**
435      * The network.
436      */
437     class AimsunModel extends EventProducer implements OTSModelInterface, EventListenerInterface
438     {
439 
440         /** */
441         private static final long serialVersionUID = 20170419L;
442 
443         /** The network. */
444         private OTSNetwork network;
445 
446         /** The simulator. */
447         private SimulatorInterface<Time, Duration, SimTimeDoubleUnit> simulator;
448 
449         /** {@inheritDoc} */
450         @Override
451         public void constructModel(final SimulatorInterface<Time, Duration, SimTimeDoubleUnit> theSimulator)
452                 throws SimRuntimeException
453         {
454             this.simulator = theSimulator;
455             // URL url = URLResource.getResource("/aimsun/singleRoad.xml");
456             XmlNetworkLaneParser nlp = new XmlNetworkLaneParser((DEVSSimulatorInterface.TimeDoubleUnit) theSimulator);
457             @SuppressWarnings("synthetic-access")
458             String xml = AimsunControl.this.networkXML;
459             try
460             {
461                 this.network = nlp.build(new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8)), true);
462             }
463             catch (NetworkException | ParserConfigurationException | SAXException | IOException | NamingException | GTUException
464                     | OTSGeometryException | ValueException | ParameterException exception)
465             {
466                 exception.printStackTrace();
467                 throw new SimRuntimeException(exception);
468             }
469         }
470 
471         /** {@inheritDoc} */
472         @Override
473         public SimulatorInterface<Time, Duration, SimTimeDoubleUnit> getSimulator()
474         {
475             return this.simulator;
476         }
477 
478         /** {@inheritDoc} */
479         @Override
480         public void notify(final EventInterface event) throws RemoteException
481         {
482             // TODO: WIP
483         }
484 
485         /** {@inheritDoc} */
486         @Override
487         public OTSNetwork getNetwork()
488         {
489             return this.network;
490         }
491 
492     }
493 
494     /** {@inheritDoc} */
495     @Override
496     protected final void addAnimationToggles()
497     {
498         AnimationToggles.setTextAnimationTogglesFull(this);
499         this.toggleAnimationClass(OTSLink.class);
500         this.toggleAnimationClass(OTSNode.class);
501         showAnimationClass(SpeedSign.class);
502     }
503 
504 }