package org.opentrafficsim.aimsun; import java.awt.Dimension; import java.awt.Frame; import java.awt.event.ActionEvent; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.PrintWriter; import java.io.Serializable; import java.net.ServerSocket; import java.net.Socket; import java.net.URISyntaxException; import java.nio.charset.StandardCharsets; import java.rmi.RemoteException; import javax.naming.NamingException; import javax.swing.JFrame; import javax.xml.bind.JAXBException; import javax.xml.parsers.ParserConfigurationException; import org.djunits.unit.TimeUnit; import org.djunits.value.ValueRuntimeException; import org.djunits.value.vdouble.scalar.Length; import org.djunits.value.vdouble.scalar.Time; import org.djutils.draw.point.OrientedPoint3d; import org.djutils.event.EventInterface; import org.djutils.event.EventListenerInterface; import org.djutils.logger.CategoryLogger; import org.djutils.logger.LogCategory; import org.opentrafficsim.aimsun.proto.AimsunControlProtoBuf; import org.opentrafficsim.aimsun.proto.AimsunControlProtoBuf.GTUPositions; import org.opentrafficsim.base.parameters.ParameterException; import org.opentrafficsim.core.animation.gtu.colorer.DefaultSwitchableGtuColorer; import org.opentrafficsim.core.dsol.AbstractOTSModel; import org.opentrafficsim.core.dsol.OTSAnimator; import org.opentrafficsim.core.dsol.OTSLoggingAnimator; import org.opentrafficsim.core.dsol.OTSModelInterface; import org.opentrafficsim.core.dsol.OTSReplication; import org.opentrafficsim.core.dsol.OTSSimulatorInterface; import org.opentrafficsim.core.gtu.Gtu; import org.opentrafficsim.core.gtu.GtuException; import org.opentrafficsim.core.gtu.GtuType; import org.opentrafficsim.core.gtu.InternalGtu; import org.opentrafficsim.core.gtu.plan.operational.OperationalPlanException; import org.opentrafficsim.core.network.Network; import org.opentrafficsim.core.network.NetworkException; import org.opentrafficsim.draw.core.OTSDrawingException; import org.opentrafficsim.draw.factory.DefaultAnimationFactory; import org.opentrafficsim.road.network.RoadNetwork; import org.opentrafficsim.road.network.defaults.RoadNetworkDefaults; import org.opentrafficsim.road.network.factory.xml.XmlParserException; import org.opentrafficsim.road.network.factory.xml.parser.RunParser; import org.opentrafficsim.road.network.factory.xml.parser.XmlNetworkLaneParser; import org.opentrafficsim.road.network.lane.CrossSectionLink; import org.opentrafficsim.road.network.lane.conflict.ConflictBuilder; import org.opentrafficsim.road.network.lane.conflict.LaneCombinationList; import org.opentrafficsim.swing.gui.OTSAnimationPanel; import org.opentrafficsim.swing.gui.OTSSimulationApplication; import org.opentrafficsim.swing.gui.OTSSwingApplication; import org.opentrafficsim.trafficcontrol.TrafficControlException; import org.opentrafficsim.xml.generated.OTS; import org.pmw.tinylog.Level; import org.xml.sax.SAXException; import nl.tudelft.simulation.dsol.SimRuntimeException; import nl.tudelft.simulation.dsol.experiment.ExperimentRunControl; import nl.tudelft.simulation.dsol.experiment.StreamSeedInformation; import nl.tudelft.simulation.dsol.simulators.DEVSRealTimeAnimator; import nl.tudelft.simulation.dsol.simulators.SimulatorInterface; import nl.tudelft.simulation.language.DSOLException; /** *

* Copyright (c) 2013-2021 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
* BSD-style license. See OpenTrafficSim License. *

* @version $Revision$, $LastChangedDate$, by $Author$, initial version Apr 18, 2017
* @author Alexander Verbraeck * @author Peter Knoppers * @author Wouter Schakel */ public class AimsunControl { /** Currently active Aimsun model. */ private AimsunModel model = null; /** * Program entry point. * @param args String[]; the command line arguments * @throws NetworkException on error * @throws NamingException on error * @throws ValueRuntimeException on error * @throws SimRuntimeException on error * @throws ParameterException on error */ public static void main(final String[] args) throws NetworkException, NamingException, ValueRuntimeException, ParameterException, SimRuntimeException { // TODO: LaneOperationalPlanBuilder.INSTANT_LANE_CHANGES = false; CategoryLogger.setAllLogLevel(Level.WARNING); CategoryLogger.setLogCategories(LogCategory.ALL); String ip = null; Integer port = null; for (String arg : args) { int equalsPos = arg.indexOf("="); if (equalsPos < 0) { System.err.println("Unhandled argument \"" + arg + "\""); } String key = arg.substring(0, equalsPos); String value = arg.substring(equalsPos + 1); switch (key.toUpperCase()) { case "IP": ip = value; break; case "PORT": try { port = Integer.parseInt(value); } catch (NumberFormatException exception) { System.err.println("Bad port number \"" + value + "\""); System.exit(1); } break; default: System.err.println("Unhandled argument \"" + arg + "\""); } } if (null == ip || null == port) { System.err.println("Missing required argument(s) ip= port="); System.exit(1); } try { System.out.println("Creating server socket for port " + port); ServerSocket serverSocket = new ServerSocket(port); serverSocket.setReuseAddress(true); // Ensure we can be restarted without the normal 2 minute delay System.out.println("Waiting for client to connect"); Socket clientSocket = serverSocket.accept(); System.out.println("Client connected; closing server socket"); serverSocket.close(); // don't accept any other connections System.out.println("Socket time out is " + clientSocket.getSoTimeout()); clientSocket.setSoTimeout(0); System.out.println("Constructing animation/simulation"); AimsunControl aimsunControl = new AimsunControl(); aimsunControl.commandLoop(clientSocket); } catch (IOException exception) { exception.printStackTrace(); } System.exit(0); } /** Shared between sendGTUPositionsToAimsun and the commandLoop methods. */ private Time simulateUntil = null; /** Shared between sendGTUPositionsToAimsun and the commandLoop methods. */ private Boolean waitingForSimulator = false; /** * Construct a GTU positions message, clear the simulateUntil value, transmit all GTU positions to Aimsun and wait for the * simulateUntil value to be set again. * @param outputStream OutputStream; */ protected void sendGTUPositionsToAimsun(final OutputStream outputStream) { OTSSimulatorInterface simulator = this.model.getSimulator(); Time stopTime = simulator.getSimulatorTime(); // System.err.println("Entering sendGTUPositionsToAimsun: simulator time is " + stopTime); AimsunControlProtoBuf.GTUPositions.Builder builder = AimsunControlProtoBuf.GTUPositions.newBuilder(); for (Gtu gtu : this.model.getNetwork().getGtus()) { AimsunControlProtoBuf.GTUPositions.GTUPosition.Builder gpb = AimsunControlProtoBuf.GTUPositions.GTUPosition.newBuilder(); gpb.setGtuId(gtu.getId()); OrientedPoint3d dp; try { dp = ((InternalGtu) gtu).getOperationalPlan().getLocation(stopTime); gpb.setX(dp.x); gpb.setY(dp.y); gpb.setZ(dp.z); gpb.setAngle(dp.getDirZ()); gpb.setLength(gtu.getLength().si); gpb.setWidth(gtu.getWidth().si); gpb.setGtuTypeId(Integer.parseInt(gtu.getGTUType().getId().split("\\.")[1])); gpb.setSpeed(gtu.getSpeed().si); builder.addGtuPos(gpb.build()); } catch (OperationalPlanException exception) { exception.printStackTrace(); } } builder.setStatus("OK"); GTUPositions gtuPositions = builder.build(); AimsunControlProtoBuf.OTSMessage.Builder resultBuilder = AimsunControlProtoBuf.OTSMessage.newBuilder(); resultBuilder.setGtuPositions(gtuPositions); AimsunControlProtoBuf.OTSMessage result = resultBuilder.build(); this.simulateUntil = null; try { transmitMessage(result, outputStream); } catch (IOException exception) { exception.printStackTrace(); } // System.err.println("sendGTUPositionsToAimsun: GTU positions sent; setting wait for next Aimsun message flag"); this.waitingForSimulator = false; // System.err.println("sendGTUPositionsToAimsun: polling simulateUntil value to become non null"); while (this.simulateUntil == null) { try { Thread.sleep(1); } catch (InterruptedException exception) { System.err.println("Interrupted exception (sendGTUPositionsToAimsun)"); // exception.printStackTrace(); } } try { simulator.scheduleEventAbs(this.simulateUntil, this, this, "sendGTUPositionsToAimsun", new Object[] {outputStream}); } catch (SimRuntimeException exception) { exception.printStackTrace(); } // System.err.println("Simulator resuming from sendGTUPositionsToAimsun event"); } /** * Process incoming commands. * @param socket Socket; the communications channel to Aimsun * @throws IOException when communication with Aimsun fails * @throws NetworkException on error * @throws NamingException on error * @throws ValueRuntimeException on error * @throws SimRuntimeException on error * @throws ParameterException on error */ private void commandLoop(final Socket socket) throws IOException, NetworkException, NamingException, ValueRuntimeException, ParameterException, SimRuntimeException { System.out.println("Entering command loop"); InputStream inputStream = socket.getInputStream(); OutputStream outputStream = socket.getOutputStream(); String error = null; boolean simulatorStarted = false; while (null == error) { try { if (this.waitingForSimulator) { // System.err.println("Polling waitingForSimulator"); // System.err.println("Simulator running status is " + this.model.getSimulator().isStartingOrRunning()); while (this.waitingForSimulator) { if (!this.model.getSimulator().isStartingOrRunning()) { System.out.println("Simulator has stopped; constructing GTUPositions message with error status"); Time stopTime = this.model.getSimulator().getSimulatorTime(); AimsunControlProtoBuf.GTUPositions.Builder builder = AimsunControlProtoBuf.GTUPositions.newBuilder(); for (Gtu gtu : this.model.getNetwork().getGtus()) { AimsunControlProtoBuf.GTUPositions.GTUPosition.Builder gpb = AimsunControlProtoBuf.GTUPositions.GTUPosition.newBuilder(); gpb.setGtuId(gtu.getId()); OrientedPoint3d dp; try { dp = ((InternalGtu) gtu).getOperationalPlan().getLocation(stopTime); gpb.setX(dp.x); gpb.setY(dp.y); gpb.setZ(dp.z); gpb.setAngle(dp.getDirZ()); gpb.setLength(gtu.getLength().si); gpb.setWidth(gtu.getWidth().si); gpb.setGtuTypeId(Integer.parseInt(gtu.getGTUType().getId().split("\\.")[1])); gpb.setSpeed(gtu.getSpeed().si); builder.addGtuPos(gpb.build()); } catch (OperationalPlanException exception) { exception.printStackTrace(); } } builder.setStatus("ERROR"); GTUPositions gtuPositions = builder.build(); AimsunControlProtoBuf.OTSMessage.Builder resultBuilder = AimsunControlProtoBuf.OTSMessage.newBuilder(); resultBuilder.setGtuPositions(gtuPositions); AimsunControlProtoBuf.OTSMessage result = resultBuilder.build(); this.simulateUntil = null; try { transmitMessage(result, outputStream); } catch (IOException exception) { exception.printStackTrace(); } System.out.println("Error message sent"); // System.err.println("Clearing waitingForSimulator flag"); this.waitingForSimulator = false; break; } try { Thread.sleep(1); } catch (InterruptedException exception) { System.err.println("Interrupted exception (command loop)"); // exception.printStackTrace(); } } // System.err.println("waitingForSimulator flag became false"); // System.err.println("Simulator running status is " + this.model.getSimulator().isStartingOrRunning()); } byte[] sizeBytes = new byte[4]; // System.err.println("CommandLoop: request fillBuffer to read 4 bytes"); fillBuffer(inputStream, sizeBytes); int size = ((sizeBytes[0] & 0xff) << 24) + ((sizeBytes[1] & 0xff) << 16) + ((sizeBytes[2] & 0xff) << 8) + (sizeBytes[3] & 0xff); // System.err.println("CommandLoop: requesting fillBuffer to read " + size + " bytes"); byte[] buffer = new byte[size]; fillBuffer(inputStream, buffer); AimsunControlProtoBuf.OTSMessage message = AimsunControlProtoBuf.OTSMessage.parseFrom(buffer); if (null == message) { System.out.println("Connection terminated; exiting"); break; } switch (message.getMsgCase()) { case CREATESIMULATION: System.out.println("Received CREATESIMULATION message"); AimsunControlProtoBuf.CreateSimulation createSimulation = message.getCreateSimulation(); String networkXML = createSimulation.getNetworkXML(); try (PrintWriter pw = new PrintWriter("c:/Temp/AimsunOtsNetwork.xml")) { pw.print(networkXML); } try { OTSAnimator animator = new OTSLoggingAnimator("C:/Temp/AimsunEventlog.txt", "AimsunControl"); OTS parsedXmlData = XmlNetworkLaneParser .parseXML(new ByteArrayInputStream(networkXML.getBytes(StandardCharsets.UTF_8))); this.model = new AimsunModel(animator, "Aimsun generated model", "Aimsun model", parsedXmlData); StreamSeedInformation streamInformation = new StreamSeedInformation(); ExperimentRunControl.TimeDoubleUnit experimentData = RunParser.parseRun("aimsun", parsedXmlData.getRUN(), streamInformation, animator); OTSReplication replication = new OTSReplication("aimsun", experimentData.getStartTime(), experimentData.getWarmupPeriod(), experimentData.getRunLength()); animator.initialize(this.model, replication); System.out.println("animator initialized"); OTSAnimationPanel animationPanel = new OTSAnimationPanel(this.model.getNetwork().getExtent(), new Dimension(1100, 1000), animator, this.model, OTSSwingApplication.DEFAULT_COLORER, this.model.getNetwork()); DefaultAnimationFactory.animateXmlNetwork(this.model.getNetwork(), new DefaultSwitchableGtuColorer()); new AimsunSwingApplication(this.model, animationPanel); JFrame frame = (JFrame) animationPanel.getParent().getParent().getParent(); frame.setExtendedState(Frame.NORMAL); frame.setSize(new Dimension(1100, 1000)); frame.setBounds(0, 25, 1100, 1000); animator.setSpeedFactor(Double.MAX_VALUE, true); animator.setSpeedFactor(1000.0, true); try { Thread.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); } animationPanel.actionPerformed(new ActionEvent(this, 0, "ZoomAll")); } catch (SimRuntimeException | NamingException | OTSDrawingException | DSOLException | XmlParserException | JAXBException | SAXException | ParserConfigurationException exception1) { exception1.printStackTrace(); // Stop the simulation error = "XML ERROR"; } break; case SIMULATEUNTIL: { AimsunControlProtoBuf.SimulateUntil simulateUntilThing = message.getSimulateUntil(); Time stopTime = new Time(simulateUntilThing.getTime(), TimeUnit.BASE_SECOND); System.out.println("Received SIMULATEUNTIL " + stopTime + " message"); OTSSimulatorInterface simulator = this.model.getSimulator(); if (!simulatorStarted) { simulatorStarted = true; simulator.scheduleEventAbs(stopTime, this, this, "sendGTUPositionsToAimsun", new Object[] {outputStream}); System.out.println("Starting simulator"); this.simulateUntil = stopTime; simulator.start(); } else if (!simulator.isStartingOrRunning()) { // Whoops: simulator has stopped error = "HMM Simulator stopped"; } else { // System.out.println("Resuming simulator by setting simulateUntil to " + stopTime); this.waitingForSimulator = true; this.simulateUntil = stopTime; } break; } case GTUPOSITIONS: System.out.println("Received GTUPOSITIONS message SHOULD NOT HAPPEN"); socket.close(); return; case MSG_NOT_SET: System.out.println("Received MSG_NOT_SET message SHOULD NOT HAPPEN"); socket.close(); return; default: System.out.println("Received unknown message SHOULD NOT HAPPEN"); socket.close(); break; } } catch (IOException exception) { exception.printStackTrace(); break; } } } /** * Transmit a message. * @param message AimsunControlProtoBuf.OTSMessage; the message * @param outputStream OutputStream; the output stream * @throws IOException when transmission fails */ private void transmitMessage(final AimsunControlProtoBuf.OTSMessage message, final OutputStream outputStream) throws IOException { int size = message.getSerializedSize(); // System.out.print("Transmitting " + message.getGtuPositions().getGtuPosCount() + " GTU positions and status \"" // + message.getGtuPositions().getStatus() + "\" encoded in " + size + " bytes ... "); byte[] sizeBytes = new byte[4]; sizeBytes[0] = (byte) ((size >> 24) & 0xff); sizeBytes[1] = (byte) ((size >> 16) & 0xff); sizeBytes[2] = (byte) ((size >> 8) & 0xff); sizeBytes[3] = (byte) (size & 0xff); outputStream.write(sizeBytes); byte[] buffer = new byte[size]; buffer = message.toByteArray(); outputStream.write(buffer); // System.out.println("Message sent"); } /** * Fill a buffer from a stream; retry until the buffer is entirely filled. * @param in InputStream; the input stream for the data * @param buffer byte[]; the buffer * @throws IOException when it is not possible to fill the entire buffer */ static void fillBuffer(final InputStream in, final byte[] buffer) throws IOException { // System.err.println("fillBuffer: attempting to read " + buffer.length + " bytes ... "); int offset = 0; while (true) { int bytesRead = in.read(buffer, offset, buffer.length - offset); if (-1 == bytesRead) { break; } offset += bytesRead; if (buffer.length == offset) { // System.err.println("fillBuffer: got all " + buffer.length + " requested bytes"); break; } if (buffer.length < offset) { System.err.println("fillBuffer: Oops: Got more than " + buffer.length + " requested bytes"); break; } // System.err.println( // "fillBuffer: now got " + offset + " bytes; need to read " + (buffer.length - offset) + " more bytes ... "); } if (offset != buffer.length) { throw new IOException("Got only " + offset + " of expected " + buffer.length + " bytes"); } } /** * The application. */ class AimsunSwingApplication extends OTSSimulationApplication { /** */ private static final long serialVersionUID = 1L; /** * @param model OTSModelInterface; the model * @param panel OTSAnimationPanel; the panel of the main screen * @throws OTSDrawingException on animation error */ AimsunSwingApplication(final OTSModelInterface model, final OTSAnimationPanel panel) throws OTSDrawingException { super(model, panel); } } /** * The Model. */ class AimsunModel extends AbstractOTSModel implements EventListenerInterface { /** */ private static final long serialVersionUID = 20170419L; /** The network. */ private RoadNetwork network; /** The XML. */ private final OTS parsedXmlData; /** * @param simulator OTSSimulatorInterface; the simulator * @param shortName String; the model name * @param description String; the model description * @param parsedXmlData OTS; the parsed XML description of the simulation model */ AimsunModel(final OTSSimulatorInterface simulator, final String shortName, final String description, final OTS parsedXmlData) { super(simulator, shortName, description); this.parsedXmlData = parsedXmlData; } /** {@inheritDoc} */ @Override public void notify(final EventInterface event) throws RemoteException { System.err.println("Received event " + event); } /** {@inheritDoc} */ @Override public void constructModel() throws SimRuntimeException { System.out.println("Constructing model"); try { this.simulator.addListener(this, DEVSRealTimeAnimator.CHANGE_SPEED_FACTOR_EVENT); this.simulator.addListener(this, SimulatorInterface.TIME_CHANGED_EVENT); } catch (RemoteException exception1) { exception1.printStackTrace(); } this.network = new RoadNetwork(getShortName(), getSimulator()); RoadNetworkDefaults.registerDefaults(this.network); try { XmlNetworkLaneParser.build(this.parsedXmlData, this.network, false); LaneCombinationList ignoreList = new LaneCombinationList(); try { // TODO: These links are Aimsun Barcelona network specific. ignoreList.addLinkCombination((CrossSectionLink) this.network.getLink("928_J5"), (CrossSectionLink) this.network.getLink("928_J6")); ignoreList.addLinkCombination((CrossSectionLink) this.network.getLink("925_J1"), (CrossSectionLink) this.network.getLink("925_J2")); } catch (NullPointerException npe) { // Ignore exception that is expected to happen when the network is NOT the Barcelona test network } LaneCombinationList permittedList = new LaneCombinationList(); ConflictBuilder.buildConflictsParallel(this.network, this.network.getGtuType(GtuType.DEFAULTS.VEHICLE), getSimulator(), new ConflictBuilder.FixedWidthGenerator(Length.instantiateSI(2.0)), ignoreList, permittedList); // new GTUDumper(simulator, Time.ZERO, Duration.instantiateSI(60), network, "C:/Temp/aimsun"); } catch (NetworkException | JAXBException | URISyntaxException | XmlParserException | SAXException | ParserConfigurationException | GtuException | IOException | TrafficControlException exception) { exception.printStackTrace(); // Abusing the SimRuntimeException to propagate the message to the main method (the problem could actually be a // parsing problem) throw new SimRuntimeException(exception); } } /** {@inheritDoc} */ @Override public Network getNetwork() { return this.network; } /** {@inheritDoc} */ @Override public Serializable getSourceId() { return "AimsunModel"; } } }