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