package seng302; import seng302.Networking.BinaryMessageEncoder; import seng302.Networking.MessageEncoders.RaceVisionByteEncoder; import seng302.Networking.MessageEncoders.XMLMessageEncoder; import seng302.Networking.Messages.BoatLocation; import seng302.Networking.Messages.Enums.MessageType; import seng302.Networking.Messages.RaceStatus; import seng302.Networking.Messages.XMLMessage; import java.io.*; import java.net.*; import java.util.concurrent.ArrayBlockingQueue; /** * TCP server to send race information to connected clients. */ public class MockOutput implements Runnable { ///Timestamp of the last sent heartbeat message. private long lastHeartbeatTime; ///Period for the heartbeat - that is, how often we send it. private double heartbeatPeriod = 5.0; ///Port to expose server on. private int serverPort = 4942; ///Socket used to listen for clients on. private ServerSocket serverSocket; ///Socket used to communicate with a client. private Socket mockSocket; ///Output stream which wraps around mockSocket outstream. private DataOutputStream outToVisualiser; ///A queue that contains items that are waiting to be sent. private ArrayBlockingQueue messagesToSendQueue = new ArrayBlockingQueue<>(99999999); ///Sequence numbers used in messages. private short messageNumber = 1; private short xmlSequenceNumber = 1; private int heartbeatSequenceNum = 1; private int boatLocationSequenceNumber = 1; private int raceStatusSequenceNumber = 1; ///Strings containing XML data as strings. private String raceXml; private String regattaXml; private String boatsXml; private boolean stop = false; //whether or not hte thread keeps running /** * Ctor. * @throws IOException if server socket cannot be opened. */ public MockOutput() throws IOException { lastHeartbeatTime = System.currentTimeMillis(); serverSocket = new ServerSocket(serverPort); } /** * Calculates the time since last heartbeat message, in seconds. * @return Time since last heartbeat message, in seconds. */ private double timeSinceHeartbeat() { long now = System.currentTimeMillis(); return (now - lastHeartbeatTime) / 1000.0; } //returns the heartbeat message /** * Increment the heartbeat value * @return message for heartbeat data */ private byte[] heartbeat(){ byte[] heartbeatMessage = RaceVisionByteEncoder.heartBeat(heartbeatSequenceNum); heartbeatSequenceNum++; BinaryMessageEncoder binaryMessageEncoder = new BinaryMessageEncoder(MessageType.HEARTBEAT, System.currentTimeMillis(), messageNumber, (short)heartbeatMessage.length, heartbeatMessage); messageNumber++; return binaryMessageEncoder.getFullMessage(); } /** * Used to give the mockOutput an xml string to be made into a message and sent * @param xmlString the xml string to send * @param messageType the kind of xml string, values given in AC35 spec (5 regatta, 6 race, 7 boat) */ public synchronized void parseXMLString(String xmlString, int messageType){ XMLMessageEncoder encoder = new XMLMessageEncoder(messageNumber, System.currentTimeMillis(), messageType, xmlSequenceNumber,(short) xmlString.length(), xmlString); //iterates the sequence numbers xmlSequenceNumber++; byte[] encodedXML = encoder.encode(); BinaryMessageEncoder binaryMessageEncoder = new BinaryMessageEncoder(MessageType.XMLMESSAGE, System.currentTimeMillis(), messageNumber, (short)encodedXML.length, encodedXML); //iterates the message number messageNumber++; addMessageToBufferToSend(binaryMessageEncoder.getFullMessage()); } /** * Used to give the mocOutput information about boat location to be made into a message and sent * @param sourceID id of the boat * @param lat latitude of boat * @param lon longitude of boat * @param heading heading of boat * @param speed speed of boat * @param time historical time of race */ public synchronized void parseBoatLocation(int sourceID, double lat, double lon, double heading, double speed, long time){ BoatLocation boatLocation = new BoatLocation(sourceID, lat, lon, boatLocationSequenceNumber, heading, speed, time); //iterates the sequence number boatLocationSequenceNumber++; //encodeds the messages byte[] encodedBoatLoc = RaceVisionByteEncoder.boatLocation(boatLocation); //encodeds the full message with header BinaryMessageEncoder binaryMessageEncoder = new BinaryMessageEncoder(MessageType.BOATLOCATION, System.currentTimeMillis(), messageNumber, (short)encodedBoatLoc.length, encodedBoatLoc); //iterates the message number messageNumber++; addMessageToBufferToSend(binaryMessageEncoder.getFullMessage()); } /** * Parse the race status data and add it to the buffer to be sent * @param raceStatus race status to parses */ public synchronized void parseRaceStatus(RaceStatus raceStatus){ //iterates the sequence number raceStatusSequenceNumber++; //encodeds the messages byte[] encodedRaceStatus = RaceVisionByteEncoder.raceStatus(raceStatus); //encodeds the full message with header BinaryMessageEncoder binaryMessageEncoder = new BinaryMessageEncoder(MessageType.RACESTATUS, System.currentTimeMillis(), messageNumber, (short)encodedRaceStatus.length, encodedRaceStatus); //iterates the message number messageNumber++; addMessageToBufferToSend(binaryMessageEncoder.getFullMessage()); } /** * Add a message to the buffer to be sent * @param messagesToSendBuffer message to add to the buffer */ private synchronized void addMessageToBufferToSend(byte[] messagesToSendBuffer) { this.messagesToSendQueue.add(messagesToSendBuffer); } /** * Sending loop of the Server */ public void run() { try { while (!stop){ System.out.println("Waiting for a connection...");//TEMP DEBUG REMOVE mockSocket = serverSocket.accept(); outToVisualiser = new DataOutputStream(mockSocket.getOutputStream()); if (boatsXml == null || regattaXml == null || raceXml == null){ try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } continue; } parseXMLString(raceXml, XMLMessage.XMLTypeRace); parseXMLString(regattaXml, XMLMessage.XMLTypeRegatta); parseXMLString(boatsXml, XMLMessage.XMLTypeBoat); while(true) { try { //Sends a heartbeat every so often. if (timeSinceHeartbeat() >= heartbeatPeriod) { outToVisualiser.write(heartbeat()); lastHeartbeatTime = System.currentTimeMillis(); } //Checks the buffer to see if there is anything to send. while (messagesToSendQueue.size() > 0) { //Grabs message from head of queue. byte[] binaryMessage = messagesToSendQueue.remove(); //sends the message to the visualiser outToVisualiser.write(binaryMessage); } }catch(SocketException e){ break; } } } } catch (IOException e) { e.printStackTrace(); } } public void stop(){ stop = true; } /** * Sets the Race XML to send * @param raceXml XML to send to the CLient */ public void setRaceXml(String raceXml) { this.raceXml = raceXml; } /** * Sets the Regatta XMl to send * @param regattaXml XML to send to CLient */ public void setRegattaXml(String regattaXml) { this.regattaXml = regattaXml; } /** * Sets the Boats XML to send * @param boatsXml XMl to send to the CLient */ public void setBoatsXml(String boatsXml) { this.boatsXml = boatsXml; } public static void main(String argv[]) throws Exception { MockOutput client = new MockOutput(); client.run(); } }