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://tudelft.nl/staff/p.knoppers-1">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 String; id of the traffic controller
72       * @param controlProgram String; name of the CCOL program that this CCOL link must communicate with
73       * @param trafficLights Set&lt;TrafficLight&gt;; the traffic lights. The ids of the traffic lights must end with two digits
74       *            that match the stream numbers as used in the traffic control program
75       * @param sensors Set&lt;TrafficLightSensor&gt;; the traffic sensors. The ids of the traffic sensors must end with three
76       *            digits; the first two of those must match the stream and sensor numbers used in the traffic control program
77       * @param simulator DevsSimulator&lt;Time, Duration, SimTimeDoubleUnit&gt;; 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 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 Socket; 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     /** {@inheritDoc} */
186     @Override
187     public void notify(final Event event) throws RemoteException
188     {
189         EventType eventType = event.getType();
190         if (eventType.equals(Replication.END_REPLICATION_EVENT))
191         {
192             if (null != this.serverSocket)
193             {
194                 try
195                 {
196                     this.serverSocket.close();
197                 }
198                 catch (IOException exception)
199                 {
200                     exception.printStackTrace();
201                 }
202                 this.serverSocket = null;
203             }
204             if (null != this.clientSocket)
205             {
206                 try
207                 {
208                     this.clientSocket.close();
209                 }
210                 catch (IOException exception)
211                 {
212                     exception.printStackTrace();
213                 }
214                 this.clientSocket = null;
215             }
216         }
217         // Tracing etc. not implemented yet.
218     }
219 
220     /** {@inheritDoc} */
221     @Override
222     public String getId()
223     {
224         return this.id;
225     }
226 
227     /** {@inheritDoc} */
228     @Override
229     public String getFullId()
230     {
231         return this.id;
232     }
233 
234     /** {@inheritDoc} */
235     @Override
236     public void updateDetector(final String detectorId, final boolean detectingGTU)
237     {
238         // FIXME: format of messages is TBD
239         String message = String.format("DET %s %s", detectorId, detectingGTU);
240         this.ccolWriter.print(message);
241     }
242 
243     /** {@inheritDoc} */
244     @Override
245     public Container getDisplayContainer()
246     {
247         return null; // For now, CCOL does not have a display panel
248     }
249 
250 }