diff --git a/src/main/java/seng302/server/ServerThread.java b/src/main/java/seng302/server/ServerThread.java index cd4c091f..f84887c2 100644 --- a/src/main/java/seng302/server/ServerThread.java +++ b/src/main/java/seng302/server/ServerThread.java @@ -1,13 +1,12 @@ package seng302.server; -import seng302.server.messages.Heartbeat; -import seng302.server.messages.Message; -import seng302.server.messages.XMLMessage; -import seng302.server.messages.XMLMessageSubType; +import seng302.server.messages.*; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; import java.util.Timer; import java.util.TimerTask; @@ -15,6 +14,7 @@ public class ServerThread implements Runnable{ private Thread runner; private StreamingServerSocket server; private final int HEARTBEAT_PERIOD = 5000; + private final int RACE_STATUS_PERIOD = 1000; private final int PORT_NUMBER = 8085; public ServerThread(String threadName){ @@ -45,6 +45,29 @@ public class ServerThread implements Runnable{ return null; } + /** + * @return A sample race status message + */ + public Message getTestRaceStatusMessage(){ + BoatSubMessage boat1 = new BoatSubMessage(1, BoatStatus.PRESTART, 0, 0, 0, + 10000, 10000); + + BoatSubMessage boat2 = new BoatSubMessage(2, BoatStatus.PRESTART, 0, 0, 0, + 10000, 10000); + + BoatSubMessage boat3 = new BoatSubMessage(3, BoatStatus.PRESTART, 0, 0, 0, + 10000, 10000); + + + List boats = new ArrayList(); + boats.add(boat1); + boats.add(boat2); + boats.add(boat3); + + return new RaceStatusMessage(1, RaceStatus.PRESTART, 1000, WindDirection.EAST, + 100, 3, RaceType.MATCH_RACE, 1, boats); + } + public void run() { try{ server = new StreamingServerSocket(PORT_NUMBER); @@ -87,6 +110,21 @@ public class ServerThread implements Runnable{ } }, 0, HEARTBEAT_PERIOD); + // Timer to send the race status messages + Timer t1 = new Timer(); + t1.schedule(new TimerTask() { + @Override + public void run() { + Message statusMessage = getTestRaceStatusMessage(); + + try { + server.send(statusMessage); + } catch (IOException e) { + e.printStackTrace(); + } + } + }, 100, RACE_STATUS_PERIOD); + } catch (IOException e) { System.err.println(e.getMessage()); } diff --git a/src/main/java/seng302/server/messages/BoatStatus.java b/src/main/java/seng302/server/messages/BoatStatus.java new file mode 100644 index 00000000..94418000 --- /dev/null +++ b/src/main/java/seng302/server/messages/BoatStatus.java @@ -0,0 +1,25 @@ +package seng302.server.messages; + +/** + * The current status of a boat + */ +public enum BoatStatus { + UNDEFINED(0), + PRESTART(1), + RACING(2), + FINISHED(3), + DNS(4), + DNF(5), + DSQ(6), + CS(7); + + private long code; + + BoatStatus(long code) { + this.code = code; + } + + public long getCode(){ + return code; + } +} diff --git a/src/main/java/seng302/server/messages/BoatSubMessage.java b/src/main/java/seng302/server/messages/BoatSubMessage.java new file mode 100644 index 00000000..d403d85b --- /dev/null +++ b/src/main/java/seng302/server/messages/BoatSubMessage.java @@ -0,0 +1,91 @@ +package seng302.server.messages; + +import java.nio.ByteBuffer; + +/** + * The status of each boat, sent within a race status message + */ +public class BoatSubMessage { + private final int MESSAGE_SIZE = 20; + + private long sourceId; + private BoatStatus boatStatus; + private long legNumber; + private long numberPenaltiesAwarded; + private long numberPenaltiesServed; + private long estimatedTimeAtNextMark; + private long estimatedTimeAtFinish; + + /** + * Boat Sub message from section 4.2 of the AC35 streaming data interface spec + * @param sourceId The source ID of the boat + * @param boatStatus The boats status + * @param legNumber The leg the boat is on (0= prestart, 1=start to first mark etc) + * @param numberPenaltiesAwarded The number of penalties awarded to the boat + * @param numberPenaltiesServed The number of penalties served to the boat + * @param estimatedTimeAtFinish The estimated time (UTC) the boat will finish the race + * @param estimatedTimeAtNextMark The estimated time (UTC) the boat will arrive at the next mark + */ + public BoatSubMessage(long sourceId, BoatStatus boatStatus, long legNumber, long numberPenaltiesAwarded, long numberPenaltiesServed, + long estimatedTimeAtFinish, long estimatedTimeAtNextMark){ + this.sourceId = sourceId; + this.boatStatus = boatStatus; + this.legNumber = legNumber; + this.numberPenaltiesAwarded = numberPenaltiesAwarded; + this.numberPenaltiesServed = numberPenaltiesServed; + this.estimatedTimeAtFinish = estimatedTimeAtFinish; + this.estimatedTimeAtNextMark = estimatedTimeAtNextMark; + } + + /** + * @return The size of this message in bytes + */ + public int getSize(){ + return MESSAGE_SIZE; + } + + /** + * @return a ByteBuffer containing this boat status message + */ + public ByteBuffer getByteBuffer(){ + ByteBuffer buff = ByteBuffer.allocate(getSize()); + int buffPos = 0; + + // Source ID, 4 bytes + buff.put(ByteBuffer.allocate(4).putInt((int) sourceId).array()); + buffPos += 4; + buff.position(buffPos); + + // Boat Status, 1 byte + buff.put(ByteBuffer.allocate(1).put((byte) boatStatus.getCode()).array()); + buffPos += 1; + buff.position(buffPos); + + // Leg number, 1 byte + buff.put(ByteBuffer.allocate(1).put((byte) legNumber).array()); + buffPos += 1; + buff.position(buffPos); + + // Number of penalties awarded, 1 byte + buff.put(ByteBuffer.allocate(1).put((byte) numberPenaltiesAwarded).array()); + buffPos += 1; + buff.position(buffPos); + + // Number of penalties served, 1 byte + buff.put(ByteBuffer.allocate(1).put((byte) numberPenaltiesServed).array()); + buffPos += 1; + buff.position(buffPos); + + // Estimated time at next mark, 6 bytes + buff.put(ByteBuffer.allocate(6).putInt((int) estimatedTimeAtNextMark).array()); + buffPos += 6; + buff.position(buffPos); + + // Estimated time at finish, 6 bytes + buff.put(ByteBuffer.allocate(6).putInt((int) estimatedTimeAtFinish).array()); + buffPos += 6; + buff.position(buffPos); + + return buff; + } +} diff --git a/src/main/java/seng302/server/messages/RaceStatus.java b/src/main/java/seng302/server/messages/RaceStatus.java new file mode 100644 index 00000000..7f123c2d --- /dev/null +++ b/src/main/java/seng302/server/messages/RaceStatus.java @@ -0,0 +1,26 @@ +package seng302.server.messages; + +/** + * The current status of the race + */ +public enum RaceStatus { + NOTACTIVE(0), + WARNING(1), // Between 3:00 and 1:00 before start + PREPARATORY(2), // Less than 1:00 before start + STARTED(3), + ABANDONED(6), + POSTPONED(7), + TERMINATED(8), + RACE_START_TIME_NOT_SET(9), + PRESTART(10); // More than 3:00 before start + + private int code; + + RaceStatus(int code){ + this.code = code; + } + + public int getCode(){ + return this.code; + } +} diff --git a/src/main/java/seng302/server/messages/RaceStatusMessage.java b/src/main/java/seng302/server/messages/RaceStatusMessage.java new file mode 100644 index 00000000..17ab82fc --- /dev/null +++ b/src/main/java/seng302/server/messages/RaceStatusMessage.java @@ -0,0 +1,130 @@ +package seng302.server.messages; + +import java.io.DataOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.List; +import java.util.zip.CRC32; + +public class RaceStatusMessage extends Message{ + private final MessageType MESSAGE_TYPE = MessageType.RACE_STATUS; + private final int MESSAGE_VERSION = 2; //Always set to 1 + private final int MESSAGE_BASE_SIZE = 24; + + // fields + private long currentTime; + private long raceId; + private RaceStatus raceStatus; + private long expectedStartTime; + private WindDirection raceWindDirection; + private long windSpeed; + private long numBoatsInRace; + private RaceType raceType; + private List boats; + private CRC32 crc; + + /** + * A message containing the current status of the race + * @param raceId The ID of the current race + * @param raceStatus The status of the race + * @param expectedStartTime The expected start time + * @param raceWindDirection The wind direction (north, east, south) + * @param windSpeed The wind speed in mm/sec + * @param numBoatsInRace The number of boats in the race + * @param raceType The race type (Match/fleet) + * @param sourceId The source of this message + * @param boats A list of boat status sub messages + */ + public RaceStatusMessage(long raceId, RaceStatus raceStatus, long expectedStartTime, WindDirection raceWindDirection, + long windSpeed, long numBoatsInRace, RaceType raceType, long sourceId, List boats){ + currentTime = System.currentTimeMillis() / 1000L; + this.raceId = raceId; + this.raceStatus = raceStatus; + this.expectedStartTime = expectedStartTime; + this.raceWindDirection = raceWindDirection; + this.windSpeed = windSpeed; + this.numBoatsInRace = numBoatsInRace; + this.raceType = raceType; + this.boats = boats; + crc = new CRC32(); + + setHeader(new Header(MESSAGE_TYPE, (int) sourceId, (short) getSize())); + } + + /** + * @return the size of this message in bytes + */ + @Override + public int getSize() { + return MESSAGE_BASE_SIZE + (20 * (int) numBoatsInRace); + } + + /** + * Send this message as a stream of bytes + * @param outputStream The output stream to send the message + */ + @Override + public void send(DataOutputStream outputStream) { + ByteBuffer buff = ByteBuffer.allocate(Header.getSize() + getSize() + 4/*CRC*/); + + buff.put(getHeader().getByteBuffer()); + buff.position(Header.getSize()); + + // Version Number, 1 byte + buff.put(ByteBuffer.allocate(1).put((byte)MESSAGE_VERSION).array()); + buff.position(Header.getSize() + 1); + + // Current time, 2 bytes + buff.put(ByteBuffer.allocate(6).putInt((int)currentTime).array()); + buff.position(Header.getSize() + 7); + + // Race id, 4 bytes + buff.put(ByteBuffer.allocate(4).putInt((int)raceId).array()); + buff.position(Header.getSize() + 11); + + // Race status, 1 byte + buff.put(ByteBuffer.allocate(1).put((byte)raceStatus.getCode()).array()); + buff.position(Header.getSize() + 12); + + // Expected start time, 6 bytes + buff.put(ByteBuffer.allocate(6).putInt((int)expectedStartTime).array()); + buff.position(Header.getSize() + 18); + + // Wind direction, 2 bytes + buff.put(ByteBuffer.allocate(2).putShort((short)raceWindDirection.getCode()).array()); + buff.position(Header.getSize() + 20); + + // Wind Speed, 2 bytes + buff.put(ByteBuffer.allocate(2).putShort((short)windSpeed).array()); + buff.position(Header.getSize() + 22); + + // Number of boats, 1 byte + buff.put(ByteBuffer.allocate(1).put((byte)numBoatsInRace).array()); + buff.position(Header.getSize() + 23); + + // Race Type, 1 byte + buff.put(ByteBuffer.allocate(1).put((byte)raceType.getCode()).array()); + buff.position(Header.getSize() + 24); + + int buffPosition = Header.getSize() + 24; + + for (BoatSubMessage boatSubMessage : boats){ + buff.put(boatSubMessage.getByteBuffer()); + buffPosition += boatSubMessage.getSize(); + buff.position(buffPosition); + } + + // calculate CRC + crc.update(buff.array()); + + // Add CRC to message + buff.put(ByteBuffer.allocate(4).putInt((short)crc.getValue()).array()); + + // Send + try { + outputStream.write(buff.array()); + } catch (IOException e) { + e.printStackTrace(); + } + } +} diff --git a/src/main/java/seng302/server/messages/RaceType.java b/src/main/java/seng302/server/messages/RaceType.java new file mode 100644 index 00000000..182b5dfd --- /dev/null +++ b/src/main/java/seng302/server/messages/RaceType.java @@ -0,0 +1,20 @@ +package seng302.server.messages; + +/** + * Enum containing the types of races + * sent by the server + */ +public enum RaceType { + MATCH_RACE(1), + FLEET_RACE(2); + + private long code; + + RaceType(long code){ + this.code = code; + } + + public long getCode(){ + return code; + } +} diff --git a/src/main/java/seng302/server/messages/WindDirection.java b/src/main/java/seng302/server/messages/WindDirection.java new file mode 100644 index 00000000..c0b8d767 --- /dev/null +++ b/src/main/java/seng302/server/messages/WindDirection.java @@ -0,0 +1,20 @@ +package seng302.server.messages; + +/** + * Enum containing the supported wind directions + */ +public enum WindDirection { + NORTH(0x0000L), + EAST(0x4000L), + SOUTH(0x8000L); + + private long code; + + WindDirection(long code) { + this.code = code; + } + + public long getCode() { + return code; + } +}