View Javadoc
1   package org.opentrafficsim.trafficcontrol.ccol;
2   
3   import java.awt.Container;
4   import java.io.BufferedReader;
5   import java.io.IOException;
6   import java.io.InputStreamReader;
7   import java.io.PrintWriter;
8   import java.net.ServerSocket;
9   import java.net.Socket;
10  import java.util.Optional;
11  import java.util.Set;
12  
13  import org.djunits.unit.DurationUnit;
14  import org.djunits.value.vdouble.scalar.Duration;
15  import org.djutils.event.Event;
16  import org.djutils.event.EventType;
17  import org.djutils.event.LocalEventProducer;
18  import org.djutils.exceptions.Try;
19  import org.opentrafficsim.base.logger.Logger;
20  import org.opentrafficsim.core.dsol.OtsSimulator;
21  import org.opentrafficsim.road.network.lane.object.detector.TrafficLightDetector;
22  import org.opentrafficsim.road.network.lane.object.trafficlight.TrafficLight;
23  import org.opentrafficsim.trafficcontrol.ActuatedTrafficController;
24  import org.opentrafficsim.trafficcontrol.TrafficControlException;
25  
26  import nl.tudelft.simulation.dsol.SimRuntimeException;
27  import nl.tudelft.simulation.dsol.experiment.Replication;
28  
29  /**
30   * Communication link with a CCOL traffic control program.
31   * <p>
32   * Copyright (c) 2013-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
33   * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
34   * </p>
35   * @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
36   * @author <a href="https://github.com/peter-knoppers">Peter Knoppers</a>
37   * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
38   */
39  public class Ccol extends LocalEventProducer implements ActuatedTrafficController
40  {
41      /** Name of this CCOL traffic controller. */
42      private final String id;
43  
44      /** The simulator. */
45      private final OtsSimulator simulator;
46  
47      /** TCP port for incoming connection. */
48      private static int port = 4321;
49  
50      /** The evaluation interval of a CCOL controller. */
51      static final Duration EVALUATION_INTERVAL = new Duration(0.1, DurationUnit.SECOND);
52  
53      /** Socket used to listen for the incoming connection from the CCOL controller. */
54      private ServerSocket serverSocket;
55  
56      /** Socket for communication with the CCOL controller. */
57      private Socket clientSocket = null;
58  
59      /** Receives data from the CCOL process. */
60      private BufferedReader ccolReader = null;
61  
62      /** Sends data to the CCOL process. */
63      private PrintWriter ccolWriter = null;
64  
65      /** Thread that blocks until accept returns. */
66      private Thread acceptThread;
67  
68      /**
69       * Construct a new CCOL communication link.
70       * @param id id of the traffic controller
71       * @param controlProgram name of the CCOL program that this CCOL link must communicate with
72       * @param trafficLights the traffic lights. The ids of the traffic lights must end with two digits that match the stream
73       *            numbers as used in the traffic control program
74       * @param sensors the traffic sensors. The ids of the traffic sensors must end with three digits; the first two of those
75       *            must match the stream and sensor numbers used in the traffic control program
76       * @param simulator the simulation engine
77       * @throws TrafficControlException on failure to initialize the connection to the external CCOL program
78       * @throws SimRuntimeException on failure to schedule the first evaluation event
79       */
80      public Ccol(final String id, final String controlProgram, final Set<TrafficLight> trafficLights,
81              final Set<TrafficLightDetector> sensors, final OtsSimulator simulator)
82              throws TrafficControlException, SimRuntimeException
83      {
84          this.id = id;
85          this.simulator = simulator;
86          try
87          {
88              // Set up a listening socket
89              this.serverSocket = new ServerSocket(port);
90              Runnable acceptTask = new Runnable()
91              {
92                  @Override
93                  public void run()
94                  {
95                      try
96                      {
97                          setClientSocket(Ccol.this.serverSocket.accept());
98                      }
99                      catch (IOException exception)
100                     {
101                         exception.printStackTrace();
102                     }
103                 }
104             };
105             this.acceptThread = new Thread(acceptTask);
106             this.acceptThread.start();
107             // Start up the external CCOL program
108             Runtime.getRuntime().exec(controlProgram + " localhost:" + this.serverSocket.getLocalPort());
109         }
110         catch (IOException e)
111         {
112             e.printStackTrace();
113         }
114         this.simulator.scheduleEventRel(Duration.ZERO, () -> Try.execute(() -> step(), "Exception during Ccol step."));
115         this.simulator.addListener(this, Replication.END_REPLICATION_EVENT);
116     }
117 
118     /**
119      * Set the client socket (called from the accept thread).
120      * @param socket the socket returned by accept
121      */
122     void setClientSocket(final Socket socket)
123     {
124         if (null != this.clientSocket)
125         {
126             Logger.ots().error("clientSocket already set");
127             return;
128         }
129         this.clientSocket = socket;
130         try
131         {
132             this.ccolReader = new BufferedReader(new InputStreamReader(this.clientSocket.getInputStream()));
133             this.ccolWriter = new PrintWriter(this.clientSocket.getOutputStream());
134         }
135         catch (IOException exception)
136         {
137             exception.printStackTrace();
138         }
139         // Close the server socket to release resources and ensure that we cannot accept further connections
140         try
141         {
142             this.serverSocket.close();
143         }
144         catch (IOException exception)
145         {
146             exception.printStackTrace();
147         }
148     }
149 
150     /**
151      * Retrieve the client socket for shutdown.
152      * @return the socket for communication with the CCOL client
153      */
154     Socket getClientSocket()
155     {
156         return this.clientSocket;
157     }
158 
159     /**
160      * Let the CCOL engine determine the new state of the traffic lights and update the traffic lights accordingly.
161      * @throws TrafficControlException when the CCOL engine reports an error or communication with the CCOL engine fails
162      * @throws SimRuntimeException when scheduling the next evaluation fails
163      */
164     @SuppressWarnings("unused")
165     private void step() throws TrafficControlException, SimRuntimeException
166     {
167         // TODO time should be formatted as date, hour, etc.
168         String message = String.format("STEP %s", this.simulator.getSimulatorTime());
169         this.ccolWriter.print(message);
170         try
171         {
172             String result = this.ccolReader.readLine();
173             // TODO parse the result and update the state of traffic lights accordingly
174             // Protocol must ensure that we know it when all updates have been received.
175         }
176         catch (IOException exception)
177         {
178             exception.printStackTrace();
179         }
180         // Schedule the next step.
181         this.simulator.scheduleEventRel(EVALUATION_INTERVAL, () -> Try.execute(() -> step(), "Exception during Ccol step."));
182     }
183 
184     @Override
185     public void notify(final Event event)
186     {
187         EventType eventType = event.getType();
188         if (eventType.equals(Replication.END_REPLICATION_EVENT))
189         {
190             if (null != this.serverSocket)
191             {
192                 try
193                 {
194                     this.serverSocket.close();
195                 }
196                 catch (IOException exception)
197                 {
198                     exception.printStackTrace();
199                 }
200                 this.serverSocket = null;
201             }
202             if (null != this.clientSocket)
203             {
204                 try
205                 {
206                     this.clientSocket.close();
207                 }
208                 catch (IOException exception)
209                 {
210                     exception.printStackTrace();
211                 }
212                 this.clientSocket = null;
213             }
214         }
215         // Tracing etc. not implemented yet.
216     }
217 
218     @Override
219     public String getId()
220     {
221         return this.id;
222     }
223 
224     @Override
225     public String getFullId()
226     {
227         return this.id;
228     }
229 
230     @Override
231     public void updateDetector(final String detectorId, final boolean detectingGTU)
232     {
233         // FIXME: format of messages is TBD
234         String message = String.format("DET %s %s", detectorId, detectingGTU);
235         this.ccolWriter.print(message);
236     }
237 
238     @Override
239     public Optional<Container> getDisplayContainer()
240     {
241         return Optional.empty(); // For now, CCOL does not have a display panel
242     }
243 
244 }