From b1922fc3fc7be6fb6d208ff5d96d745d97ef43ab Mon Sep 17 00:00:00 2001 From: fjc40 Date: Sun, 6 Aug 2017 13:38:27 +1200 Subject: [PATCH] Added RaceStatusEncoder. Refactored RaceStatusDecoder to be more consistent with other decoders - it now has a getMessage() function. Added BoatStatus encoder and decoder - the RaceStatus encoder and decoder uses this for BoatStatuses. The BoatStatus encoder doesn't implement the MessageEncoder interface as BoatStatus is not a proper message type (doesn't inherit from AC35Data). Added remaining cases to EncoderFactory, but commented them out. BoatStatus now uses BoatStatusEnum instead of a byte. Added some comments to RaceStatus, and it uses enums instead of bytes. MockOutput logs a warning if a RaceStatus cannot be encoded. Added a BoatStatusDecoderTest. Updated RaceStatusDecoder to use new encoders/decoders. issue #35 #36 #story[1095] --- .../src/main/java/mock/app/MockOutput.java | 15 +- .../src/main/java/mock/model/RaceServer.java | 5 +- .../java/network/BinaryMessageDecoder.java | 2 +- .../MessageDecoders/BoatStatusDecoder.java | 90 +++++++++ .../MessageDecoders/RaceStatusDecoder.java | 177 +++++++++--------- .../MessageEncoders/BoatStatusEncoder.java | 56 ++++++ .../MessageEncoders/EncoderFactory.java | 18 ++ .../MessageEncoders/HeartBeatEncoder.java | 1 - .../MessageEncoders/RaceStatusEncoder.java | 90 +++++++++ .../RaceVisionByteEncoder.java | 51 ----- .../java/network/Messages/BoatLocation.java | 18 +- .../java/network/Messages/BoatStatus.java | 8 +- .../network/Messages/Enums/MessageType.java | 8 +- .../java/network/Messages/RaceStatus.java | 137 +++++++++++--- .../java/visualiser/model/VisualiserRace.java | 4 +- .../BoatStatusDecoderTest.java | 90 +++++++++ .../RaceStatusDecoderTest.java | 103 +++++++--- 17 files changed, 659 insertions(+), 214 deletions(-) create mode 100644 racevisionGame/src/main/java/network/MessageDecoders/BoatStatusDecoder.java create mode 100644 racevisionGame/src/main/java/network/MessageEncoders/BoatStatusEncoder.java create mode 100644 racevisionGame/src/main/java/network/MessageEncoders/RaceStatusEncoder.java create mode 100644 racevisionGame/src/test/java/network/MessageDecoders/BoatStatusDecoderTest.java diff --git a/racevisionGame/src/main/java/mock/app/MockOutput.java b/racevisionGame/src/main/java/mock/app/MockOutput.java index 2119fa06..328c5e79 100644 --- a/racevisionGame/src/main/java/mock/app/MockOutput.java +++ b/racevisionGame/src/main/java/mock/app/MockOutput.java @@ -185,11 +185,12 @@ public class MockOutput implements Runnable * Encodes/serialises a RaceStatus message, and returns it. * @param raceStatus The RaceStatus message to serialise. * @return The RaceStatus message in a serialised form. + * @throws InvalidMessageException Thrown if the message cannot be encoded. */ - private synchronized byte[] parseRaceStatus(RaceStatus raceStatus){ + private synchronized byte[] parseRaceStatus(RaceStatus raceStatus) throws InvalidMessageException { //Encodes the messages. - byte[] encodedRaceStatus = RaceVisionByteEncoder.raceStatus(raceStatus); + byte[] encodedRaceStatus = RaceVisionByteEncoder.encode(raceStatus); //Encodes the full message with header. BinaryMessageEncoder binaryMessageEncoder = new BinaryMessageEncoder( @@ -284,9 +285,15 @@ public class MockOutput implements Runnable //Sends the RaceStatus message. if (this.latestMessages.getRaceStatus() != null) { - byte[] raceStatusBlob = this.parseRaceStatus(this.latestMessages.getRaceStatus()); - this.outToVisualiser.write(raceStatusBlob); + try { + byte[] raceStatusBlob = this.parseRaceStatus(this.latestMessages.getRaceStatus()); + + this.outToVisualiser.write(raceStatusBlob); + + } catch (InvalidMessageException e) { + Logger.getGlobal().log(Level.WARNING, "Could not encode RaceStatus: " + latestMessages.getRaceStatus(), e); + } } //Send all of the BoatLocation messages. diff --git a/racevisionGame/src/main/java/mock/model/RaceServer.java b/racevisionGame/src/main/java/mock/model/RaceServer.java index c7e3ab69..b68ada32 100644 --- a/racevisionGame/src/main/java/mock/model/RaceServer.java +++ b/racevisionGame/src/main/java/mock/model/RaceServer.java @@ -139,13 +139,14 @@ public class RaceServer { //Create race status object, and send it. RaceStatus raceStatus = new RaceStatus( + RaceStatus.currentMessageVersionNumber, System.currentTimeMillis(), race.getRaceId(), - race.getRaceStatusEnum().getValue(), + race.getRaceStatusEnum(), race.getRaceClock().getStartingTimeMilli(), windDirectionInt, windSpeedInt, - race.getRaceType().getValue(), + race.getRaceType(), boatStatuses); this.latestMessages.setRaceStatus(raceStatus); diff --git a/racevisionGame/src/main/java/network/BinaryMessageDecoder.java b/racevisionGame/src/main/java/network/BinaryMessageDecoder.java index 34a5d88d..eb9c6997 100644 --- a/racevisionGame/src/main/java/network/BinaryMessageDecoder.java +++ b/racevisionGame/src/main/java/network/BinaryMessageDecoder.java @@ -139,7 +139,7 @@ public class BinaryMessageDecoder { case RACESTATUS: //System.out.println("Race Status Message"); RaceStatusDecoder rsdecoder = new RaceStatusDecoder(messageBody); - return new RaceStatus(rsdecoder.getTime(), rsdecoder.getRace(), rsdecoder.getRaceState(), rsdecoder.getStartTime(), rsdecoder.getRaceWindDir(), rsdecoder.getRaceWindSpeed(), rsdecoder.getRaceType(), rsdecoder.getBoats()); + return rsdecoder.getMessage(); case DISPLAYTEXTMESSAGE: //System.out.println("Display Text Message"); diff --git a/racevisionGame/src/main/java/network/MessageDecoders/BoatStatusDecoder.java b/racevisionGame/src/main/java/network/MessageDecoders/BoatStatusDecoder.java new file mode 100644 index 00000000..dd482486 --- /dev/null +++ b/racevisionGame/src/main/java/network/MessageDecoders/BoatStatusDecoder.java @@ -0,0 +1,90 @@ +package network.MessageDecoders; + + +import network.Messages.BoatStatus; +import network.Messages.Enums.BoatStatusEnum; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import static network.Utils.ByteConverter.*; + + +/** + * Decodes {@link BoatStatus} messages. + */ +public class BoatStatusDecoder { + + /** + * The encoded message. + */ + private byte[] encodedMessage; + + /** + * The decoded message. + */ + private BoatStatus message; + + + + /** + * Constructs a decoder to decode a given message. + * @param encodedMessage The message to decode. + */ + public BoatStatusDecoder(byte[] encodedMessage) { + this.encodedMessage = encodedMessage; + + decode(); + } + + + /** + * Decodes the contained message. + */ + private void decode() { + + + byte[] sourceIDBytes = Arrays.copyOfRange(encodedMessage, 0, 4); + int sourceID = bytesToInt(sourceIDBytes); + + byte[] boatStatusBytes = Arrays.copyOfRange(encodedMessage, 4, 5); + BoatStatusEnum boatStatus = BoatStatusEnum.fromByte(boatStatusBytes[0]); + + byte[] legNumberBytes = Arrays.copyOfRange(encodedMessage, 5, 6); + byte legNumber = legNumberBytes[0]; + + byte[] numPenaltiesAwardedBytes = Arrays.copyOfRange(encodedMessage, 6, 7); + byte numPenaltiesAwarded = numPenaltiesAwardedBytes[0]; + + byte[] numPenaltiesServedBytes = Arrays.copyOfRange(encodedMessage, 7, 8); + byte numPenaltiesServed = numPenaltiesServedBytes[0]; + + byte[] estTimeAtNextMarkBytes = Arrays.copyOfRange(encodedMessage, 8, 14); + long estTimeAtNextMark = bytesToLong(estTimeAtNextMarkBytes); + + byte[] estTimeAtFinishBytes = Arrays.copyOfRange(encodedMessage, 14, 20); + long estTimeAtFinish = bytesToLong(estTimeAtFinishBytes); + + message = new BoatStatus( + sourceID, + boatStatus, + legNumber, + numPenaltiesAwarded, + numPenaltiesServed, + estTimeAtNextMark, + estTimeAtFinish ); + + + } + + + + /** + * Returns the decoded message. + * @return The decoded message. + */ + public BoatStatus getMessage() { + return message; + } +} diff --git a/racevisionGame/src/main/java/network/MessageDecoders/RaceStatusDecoder.java b/racevisionGame/src/main/java/network/MessageDecoders/RaceStatusDecoder.java index e4d147df..4b67daa5 100644 --- a/racevisionGame/src/main/java/network/MessageDecoders/RaceStatusDecoder.java +++ b/racevisionGame/src/main/java/network/MessageDecoders/RaceStatusDecoder.java @@ -2,9 +2,13 @@ package network.MessageDecoders; import network.Messages.BoatStatus; +import network.Messages.Enums.RaceStatusEnum; +import network.Messages.Enums.RaceTypeEnum; +import network.Messages.RaceStatus; import java.util.ArrayList; import java.util.Arrays; +import java.util.List; import static network.Utils.ByteConverter.bytesToInt; import static network.Utils.ByteConverter.bytesToLong; @@ -12,110 +16,101 @@ import static network.Utils.ByteConverter.bytesToShort; /** - * Created by hba56 on 21/04/17. + * Decodes {@link RaceStatus} messages. */ public class RaceStatusDecoder { - private byte versionNum; - private byte[] timeBytes; - private byte[] raceID; - private byte raceStatus; - private byte[] expectedStart; - private byte[] raceWind; - private byte[] windSpeed; - private byte numBoats; - private byte bytesRaceType; - private byte[] boatsBytes; - - private long time; - private int race; - private byte raceState; - private long startTime; - private int raceWindDir; - private short raceWindSpeed; - private int numberOfBoats; - private int raceType; - private ArrayList boats = new ArrayList<>(); - - - public RaceStatusDecoder(byte[] encodedRaceStatus){ - versionNum = encodedRaceStatus[0]; - timeBytes = Arrays.copyOfRange(encodedRaceStatus, 1, 7); - raceID = Arrays.copyOfRange(encodedRaceStatus, 7, 11); - raceStatus = encodedRaceStatus[11]; - expectedStart = Arrays.copyOfRange(encodedRaceStatus, 12, 18); - raceWind = Arrays.copyOfRange(encodedRaceStatus, 18, 20); - windSpeed = Arrays.copyOfRange(encodedRaceStatus, 20, 22); - numBoats = encodedRaceStatus[22]; - bytesRaceType = encodedRaceStatus[23]; - boatsBytes = Arrays.copyOfRange(encodedRaceStatus, 24, 25+20*this.numBoats); - - time = bytesToLong(timeBytes); - race = bytesToInt(raceID); - raceState = raceStatus; - startTime = bytesToLong(expectedStart); - raceWindDir = bytesToInt(raceWind); - raceWindSpeed = bytesToShort(windSpeed); - numberOfBoats = bytesToInt(numBoats); - - int boatLoopIndex = 0; - - for (int i=0; i < numberOfBoats; i++) { - byte[] boatBytes = Arrays.copyOfRange(boatsBytes, boatLoopIndex, boatLoopIndex+20); - - byte[] sourceID = Arrays.copyOfRange(boatBytes, 0, 3); - byte boatStatus = boatBytes[4]; - byte legNumber = boatBytes[5]; - byte numPenaltiesAwarded = boatBytes[6]; - byte numPenaltiesServed = boatBytes[7]; - byte[] estTimeAtNextMark = Arrays.copyOfRange(boatBytes, 8, 14); - byte[] estTimeAtFinish = Arrays.copyOfRange(boatBytes, 14, 20); - - BoatStatus boat = new BoatStatus(bytesToInt(sourceID),boatStatus, - legNumber, numPenaltiesAwarded, numPenaltiesServed, - bytesToLong(estTimeAtNextMark), bytesToLong(estTimeAtFinish)); - - boats.add(boat); - boatLoopIndex += 20; - } - } - public byte getVersionNum() { - return versionNum; - } + /** + * The encoded message. + */ + private byte[] encodedMessage; - public long getTime() { - return time; - } + /** + * The decoded message. + */ + private RaceStatus message; - public int getRace() { - return race; - } - public byte getRaceState() { - return raceState; - } - public long getStartTime() { - return startTime; - } + /** + * Constructs a decoder to decode a given message. + * @param encodedMessage The message to decode. + */ + public RaceStatusDecoder(byte[] encodedMessage) { + this.encodedMessage = encodedMessage; - public int getRaceWindDir() { - return raceWindDir; + decode(); } - public short getRaceWindSpeed() { - return raceWindSpeed; - } - public int getNumberOfBoats() { - return numberOfBoats; - } + /** + * Decodes the contained message. + */ + private void decode() { + + + byte[] versionNumBytes = Arrays.copyOfRange(encodedMessage, 0, 1); + byte versionNum = versionNumBytes[0]; + + byte[] timeBytes = Arrays.copyOfRange(encodedMessage, 1, 7); + long time = bytesToLong(timeBytes); + + byte[] raceIDBytes = Arrays.copyOfRange(encodedMessage, 7, 11); + int raceID = bytesToInt(raceIDBytes); + + byte[] raceStatusBytes = Arrays.copyOfRange(encodedMessage, 11, 12); + RaceStatusEnum raceStatus = RaceStatusEnum.fromByte(raceStatusBytes[0]); + + byte[] expectedStartBytes = Arrays.copyOfRange(encodedMessage, 12, 18); + long expectedStart = bytesToLong(expectedStartBytes); - public int getRaceType() { - return raceType; + byte[] windDirectionBytes = Arrays.copyOfRange(encodedMessage, 18, 20); + int windDirection = bytesToInt(windDirectionBytes); + + byte[] windSpeedBytes = Arrays.copyOfRange(encodedMessage, 20, 22); + short windSpeed = bytesToShort(windSpeedBytes); + + byte[] numberOfBoatsBytes = Arrays.copyOfRange(encodedMessage, 22, 23); + int numberOfBoats = bytesToInt(numberOfBoatsBytes); + + byte[] raceTypeBytes = Arrays.copyOfRange(encodedMessage, 23, 24); + RaceTypeEnum raceType = RaceTypeEnum.fromByte(raceTypeBytes[0]); + + byte[] boatStatusesBytes = Arrays.copyOfRange(encodedMessage, 24, 25 + 20 * numberOfBoats); + List boatStatuses = new ArrayList<>(); + + + + //Decode each BoatStatus. + for (int boatLoopIndex=0; boatLoopIndex < (numberOfBoats * 20); boatLoopIndex += 20) { + + byte[] boatStatusBytes = Arrays.copyOfRange(boatStatusesBytes, boatLoopIndex, boatLoopIndex + 20); + + BoatStatusDecoder boatStatusDecoder = new BoatStatusDecoder(boatStatusBytes); + + boatStatuses.add(boatStatusDecoder.getMessage()); + } + + + message = new RaceStatus( + versionNum, + time, + raceID, + raceStatus, + expectedStart, + windDirection, + windSpeed, + raceType, + boatStatuses ); } - public ArrayList getBoats() { - return boats; + + + /** + * Returns the decoded message. + * @return The decoded message. + */ + public RaceStatus getMessage() { + return message; } } diff --git a/racevisionGame/src/main/java/network/MessageEncoders/BoatStatusEncoder.java b/racevisionGame/src/main/java/network/MessageEncoders/BoatStatusEncoder.java new file mode 100644 index 00000000..3a40cbba --- /dev/null +++ b/racevisionGame/src/main/java/network/MessageEncoders/BoatStatusEncoder.java @@ -0,0 +1,56 @@ +package network.MessageEncoders; + + +import network.Messages.BoatStatus; + +import java.nio.ByteBuffer; + +import static network.Utils.ByteConverter.intToBytes; +import static network.Utils.ByteConverter.longToBytes; + +/** + * This encoder can encode a {@link BoatStatus} message. + */ +public class BoatStatusEncoder { + + + /** + * Constructor. + */ + public BoatStatusEncoder() { + } + + + /** + * Encodes a given BoatStatus message. + * @param message The message to encode. + * @return The encoded message. + */ + public byte[] encode(BoatStatus message) { + + //Downcast. + BoatStatus boatStatus = (BoatStatus) message; + + //BoatStatus is 20 bytes. + ByteBuffer boatStatusBuffer = ByteBuffer.allocate(20); + + byte[] sourceID = intToBytes(boatStatus.getSourceID()); + byte[] boatStatusBytes = intToBytes(boatStatus.getBoatStatus().getValue(), 1); + byte[] legNum = intToBytes(boatStatus.getLegNumber(), 1); + byte[] numPenalties = intToBytes(boatStatus.getNumPenaltiesAwarded(), 1); + byte[] numPenaltiesServed = intToBytes(boatStatus.getNumPenaltiesServed(), 1); + byte[] estNextMarkTime = longToBytes(boatStatus.getEstTimeAtNextMark(), 6); + byte[] estFinishTime = longToBytes(boatStatus.getEstTimeAtFinish(), 6); + + boatStatusBuffer.put(sourceID); + boatStatusBuffer.put(boatStatusBytes); + boatStatusBuffer.put(legNum); + boatStatusBuffer.put(numPenalties); + boatStatusBuffer.put(numPenaltiesServed); + boatStatusBuffer.put(estNextMarkTime); + boatStatusBuffer.put(estFinishTime); + + return boatStatusBuffer.array(); + + } +} diff --git a/racevisionGame/src/main/java/network/MessageEncoders/EncoderFactory.java b/racevisionGame/src/main/java/network/MessageEncoders/EncoderFactory.java index 52bff99a..c026efd7 100644 --- a/racevisionGame/src/main/java/network/MessageEncoders/EncoderFactory.java +++ b/racevisionGame/src/main/java/network/MessageEncoders/EncoderFactory.java @@ -31,10 +31,28 @@ public class EncoderFactory { case HEARTBEAT: return new HeartBeatEncoder(); + case RACESTATUS: return new RaceStatusEncoder(); + + //case DISPLAYTEXTMESSAGE: return new DisplayTextMessageEncoder();//TODO + case XMLMESSAGE: return new XMLMessageEncoder(); + //case RACESTARTSTATUS: return new RaceStartStatusEncoder();//TODO + + //case YACHTEVENTCODE: return new YachtEventCodeEncoder();//TODO + + //case YACHTACTIONCODE: return new YachtActionCodeEncoder();//TODO + + //case CHATTERTEXT: return new ChatterTextEncoder();//TODO + case BOATLOCATION: return new BoatLocationEncoder(); + //case MARKROUNDING: return new MarkRoundingEncoder();//TODO + + //case COURSEWIND: return new CourseWindEncoder();//TODO + + //case AVGWIND: return new AverageWindEncoder();//TODO + case REQUEST_TO_JOIN: return new RequestToJoinEncoder(); case JOIN_ACCEPTANCE: return new JoinAcceptanceEncoder(); diff --git a/racevisionGame/src/main/java/network/MessageEncoders/HeartBeatEncoder.java b/racevisionGame/src/main/java/network/MessageEncoders/HeartBeatEncoder.java index 934d1217..dcf64b61 100644 --- a/racevisionGame/src/main/java/network/MessageEncoders/HeartBeatEncoder.java +++ b/racevisionGame/src/main/java/network/MessageEncoders/HeartBeatEncoder.java @@ -6,7 +6,6 @@ import network.Messages.HeartBeat; import java.nio.ByteBuffer; -import static network.Utils.ByteConverter.intToBytes; import static network.Utils.ByteConverter.longToBytes; /** diff --git a/racevisionGame/src/main/java/network/MessageEncoders/RaceStatusEncoder.java b/racevisionGame/src/main/java/network/MessageEncoders/RaceStatusEncoder.java new file mode 100644 index 00000000..7a0be153 --- /dev/null +++ b/racevisionGame/src/main/java/network/MessageEncoders/RaceStatusEncoder.java @@ -0,0 +1,90 @@ +package network.MessageEncoders; + + +import network.Messages.AC35Data; +import network.Messages.BoatStatus; +import network.Messages.RaceStatus; + +import java.nio.ByteBuffer; +import java.util.List; + +import static network.Utils.ByteConverter.intToBytes; +import static network.Utils.ByteConverter.longToBytes; + +/** + * This encoder can encode a {@link RaceStatus} message. + */ +public class RaceStatusEncoder implements MessageEncoder { + + + /** + * Constructor. + */ + public RaceStatusEncoder() { + } + + + @Override + public byte[] encode(AC35Data message) { + + //Downcast. + RaceStatus raceStatus = (RaceStatus) message; + + + List boatStatuses = raceStatus.getBoatStatuses(); + + //24 byte header, plus 20 bytes per boat status. + ByteBuffer raceStatusMessage = ByteBuffer.allocate(24 + 20 * boatStatuses.size()); + + //Version Number 1 bytes. this changes with the pdf. (2) + byte versionNum = 0b10; + + //time (6 bytes) + byte[] timeBytes = longToBytes(raceStatus.getCurrentTime(), 6); + + //race identifier in case multiple races are going at once. + byte[] raceID = intToBytes(raceStatus.getRaceID()); + + //race status 0 - 10 + byte[] raceStatusByte = intToBytes(raceStatus.getRaceStatus().getValue(), 1); + + //number of milliseconds from Jan 1, 1970 for when the data is valid + byte[] expectedStart = longToBytes(raceStatus.getExpectedStartTime(), 6); + + //North = 0x0000 East = 0x4000 South = 0x8000. + byte[] raceWind = intToBytes(raceStatus.getWindDirection(), 2); + + //mm/sec + byte[] windSpeed = intToBytes(raceStatus.getWindSpeed(), 2); + + + byte[] numBoats = intToBytes(boatStatuses.size(), 1); + + //1 match race, 2 fleet race + byte[] bytesRaceType = intToBytes(raceStatus.getRaceType().getValue(), 1); + + + raceStatusMessage.put(versionNum); + raceStatusMessage.put(timeBytes); + raceStatusMessage.put(raceID); + raceStatusMessage.put(raceStatusByte); + raceStatusMessage.put(expectedStart); + raceStatusMessage.put(raceWind); + raceStatusMessage.put(windSpeed); + raceStatusMessage.put(numBoats); + raceStatusMessage.put(bytesRaceType); + + //Encode each BoatStatus. + for (BoatStatus boatStatus : boatStatuses) { + + BoatStatusEncoder boatStatusEncoder = new BoatStatusEncoder(); + + byte[] boatStatusEncoded = boatStatusEncoder.encode(boatStatus); + + raceStatusMessage.put(boatStatusEncoded); + } + + return raceStatusMessage.array(); + + } +} diff --git a/racevisionGame/src/main/java/network/MessageEncoders/RaceVisionByteEncoder.java b/racevisionGame/src/main/java/network/MessageEncoders/RaceVisionByteEncoder.java index b6b7c774..ebd67819 100644 --- a/racevisionGame/src/main/java/network/MessageEncoders/RaceVisionByteEncoder.java +++ b/racevisionGame/src/main/java/network/MessageEncoders/RaceVisionByteEncoder.java @@ -21,57 +21,6 @@ public class RaceVisionByteEncoder { - /** - * Serializes a RaceStatus message. - * @param raceStatus Message to serialize. - * @return Serialized (byte array) message, ready to be written to a socket. - */ - public static byte[] raceStatus(RaceStatus raceStatus){ - - List boatStatuses = raceStatus.getBoatStatuses(); - - ByteBuffer raceStatusMessage = ByteBuffer.allocate(24 + 20* boatStatuses.size()); - //Version Number 1 bytes - byte versionNum = 0b10; //this changes with the pdf. (2) - byte[] timeBytes = longToBytes(raceStatus.getCurrentTime(), 6);//time (6 bytes) - byte[] raceID = ByteBuffer.allocate(4).put(intToBytes(raceStatus.getRaceID())).array();//race identifier incase multiple races are going at once. - byte[] raceStatusByte = intToBytes(raceStatus.getRaceStatus(), 1);//race status 0 - 10 - byte[] expectedStart = longToBytes(raceStatus.getExpectedStartTime(), 6);//number of milliseconds from Jan 1, 1970 for when the data is valid - byte[] raceWind = ByteBuffer.allocate(2).put(intToBytes(raceStatus.getWindDirection(), 2)).array();//North = 0x0000 East = 0x4000 South = 0x8000. - byte[] windSpeed = ByteBuffer.allocate(2).put(intToBytes(raceStatus.getWindSpeed(), 2)).array();//mm/sec - byte[] numBoats = intToBytes(boatStatuses.size(), 1); - byte[] bytesRaceType = intToBytes(raceStatus.getRaceType(), 1);//1 match race, 2 fleet race - - raceStatusMessage.put(versionNum); - raceStatusMessage.put(timeBytes); - raceStatusMessage.put(raceID); - raceStatusMessage.put(raceStatusByte); - raceStatusMessage.put(expectedStart); - raceStatusMessage.put(raceWind); - raceStatusMessage.put(windSpeed); - raceStatusMessage.put(numBoats); - raceStatusMessage.put(bytesRaceType); - - for (int i = 0; i < boatStatuses.size(); i++){ - byte[] sourceID = intToBytes(boatStatuses.get(i).getSourceID()); - byte[] boatStatus = intToBytes(boatStatuses.get(i).getBoatStatus(), 1); - byte[] legNum = intToBytes(boatStatuses.get(i).getLegNumber(), 1); - byte[] numPenalties = intToBytes(boatStatuses.get(i).getNumPenaltiesAwarded(), 1); - byte[] numPenaltiesServed = intToBytes(boatStatuses.get(i).getNumPenaltiesServed(), 1); - byte[] estNextMarkTime = longToBytes(boatStatuses.get(i).getEstTimeAtNextMark(), 6); - byte[] estFinishTime = longToBytes( boatStatuses.get(i).getEstTimeAtFinish(), 6); - - raceStatusMessage.put(sourceID); - raceStatusMessage.put(boatStatus); - raceStatusMessage.put(legNum); - raceStatusMessage.put(numPenalties); - raceStatusMessage.put(numPenaltiesServed); - raceStatusMessage.put(estNextMarkTime); - raceStatusMessage.put(estFinishTime); - } - - return raceStatusMessage.array(); - } public static byte[] displayTextMessage(RaceMessage[] message){ //ByteBuffer result = ByteBuffer.allocate(4 + numLines * 32); diff --git a/racevisionGame/src/main/java/network/Messages/BoatLocation.java b/racevisionGame/src/main/java/network/Messages/BoatLocation.java index 301584b4..1f2bfc49 100644 --- a/racevisionGame/src/main/java/network/Messages/BoatLocation.java +++ b/racevisionGame/src/main/java/network/Messages/BoatLocation.java @@ -29,8 +29,14 @@ public class BoatLocation extends AC35Data { public static final byte Helicopter = 12; public static final byte DataProcessingApplication = 13; - ///Version number of the message - is always 1. - private byte messageVersionNumber = 1; + + /** + * The current messageVersionNumber according to the API spec. + */ + public static final byte currentMessageVersionNumber = 1; + + ///Version number of the message. + private byte messageVersionNumber; ///Time of the event - milliseconds since jan 1 1970. Proper type is 6 byte int. private long time; ///Source ID of the boat. @@ -91,12 +97,6 @@ public class BoatLocation extends AC35Data { private short rudderAngle; - /** - * Ctor. Default. - */ - public BoatLocation() { - super(MessageType.BOATLOCATION); - } /** * Ctor, with all parameters. @@ -154,7 +154,7 @@ public class BoatLocation extends AC35Data { public BoatLocation(int sourceID, double lat, double lon, long sequenceNumber, double heading, double boatSpeed, long time) { super(MessageType.BOATLOCATION); - this.messageVersionNumber = (byte) 1; + this.messageVersionNumber = BoatLocation.currentMessageVersionNumber; this.time = time; this.sourceID = sourceID; this.sequenceNumber = sequenceNumber; diff --git a/racevisionGame/src/main/java/network/Messages/BoatStatus.java b/racevisionGame/src/main/java/network/Messages/BoatStatus.java index 54996726..277ae9ca 100644 --- a/racevisionGame/src/main/java/network/Messages/BoatStatus.java +++ b/racevisionGame/src/main/java/network/Messages/BoatStatus.java @@ -10,14 +10,14 @@ import network.Utils.ByteConverter; public class BoatStatus { private int sourceID; - private byte boatStatus; + private BoatStatusEnum boatStatus; private byte legNumber; private byte numPenaltiesAwarded; private byte numPenaltiesServed; private long estTimeAtNextMark; private long estTimeAtFinish; - public BoatStatus(int sourceID, byte boatStatus, byte legNumber, byte numPenaltiesAwarded, byte numPenaltiesServed, long estTimeAtNextMark, long estTimeAtFinish) { + public BoatStatus(int sourceID, BoatStatusEnum boatStatus, byte legNumber, byte numPenaltiesAwarded, byte numPenaltiesServed, long estTimeAtNextMark, long estTimeAtFinish) { this.sourceID = sourceID; this.boatStatus = boatStatus; this.legNumber = legNumber; @@ -30,7 +30,7 @@ public class BoatStatus { public BoatStatus(int sourceID, BoatStatusEnum boatStatusEnum, int legNum, long estTimeAtNextMark) { this.sourceID = sourceID; - this.boatStatus = boatStatusEnum.getValue(); + this.boatStatus = boatStatusEnum; this.legNumber = ByteConverter.intToBytes(legNum)[0]; this.numPenaltiesAwarded = 0; this.numPenaltiesServed = 0; @@ -43,7 +43,7 @@ public class BoatStatus { return sourceID; } - public byte getBoatStatus() { + public BoatStatusEnum getBoatStatus() { return boatStatus; } diff --git a/racevisionGame/src/main/java/network/Messages/Enums/MessageType.java b/racevisionGame/src/main/java/network/Messages/Enums/MessageType.java index 4072f912..15f70f40 100644 --- a/racevisionGame/src/main/java/network/Messages/Enums/MessageType.java +++ b/racevisionGame/src/main/java/network/Messages/Enums/MessageType.java @@ -33,11 +33,15 @@ public enum MessageType { BOATACTION(100), NOTAMESSAGE(0); - ///Primitive value of the enum. + + /** + * Primitive value of the enum. + */ private byte value; + /** - * Ctor. Creates a MessageType enum from a given primitive integer value, cast to a byte. + * Creates a MessageType enum from a given primitive integer value, cast to a byte. * @param value Integer, which is cast to byte, to construct from. */ private MessageType(int value) { diff --git a/racevisionGame/src/main/java/network/Messages/RaceStatus.java b/racevisionGame/src/main/java/network/Messages/RaceStatus.java index 42bb8208..4d98700d 100644 --- a/racevisionGame/src/main/java/network/Messages/RaceStatus.java +++ b/racevisionGame/src/main/java/network/Messages/RaceStatus.java @@ -2,27 +2,89 @@ package network.Messages; import network.Messages.Enums.MessageType; +import network.Messages.Enums.RaceStatusEnum; +import network.Messages.Enums.RaceTypeEnum; import network.Utils.AC35UnitConverter; import shared.model.Constants; import java.util.List; /** - * Created by fwy13 on 25/04/17. + * Represents the information in a RaceStatus message (AC streaming spec: 4.2). */ public class RaceStatus extends AC35Data { + + /** + * The current messageVersionNumber according to the API spec. + */ + public static final byte currentMessageVersionNumber = 2; + + + /** + * Version number of the message. + */ + private byte messageVersionNumber; + + /** + * Time the message was generated at. + * Milliseconds since unix epoch. + */ private long currentTime; + + /** + * ID number of the race. + */ private int raceID; - private byte raceStatus; + + /** + * The status of the race. + */ + private RaceStatusEnum raceStatus; + + /** + * The expected race start time. + * Milliseconds since unix epoch. + */ private long expectedStartTime; + + /** + * The wind direction of the course. + */ private int windDirection; + + /** + * The wind speed of the course. + */ private int windSpeed; - private int raceType; + + /** + * The type of race this is. + */ + private RaceTypeEnum raceType; + + /** + * A list of boat statuses. + * One for each boat. + */ private List boatStatuses; - public RaceStatus(long currentTime, int raceID, byte raceStatus, long expectedStartTime, int windDirection, int windSpeed, int raceType, List boatStatuses){ + + /** + * Constructs a RaceStatus message with the given parameters. + * @param messageVersionNumber The version number of the message. + * @param currentTime Time at which the message was generated. + * @param raceID The ID of the race. + * @param raceStatus The status of the race. + * @param expectedStartTime The expected start time of the race. + * @param windDirection The current wind direction in the race. + * @param windSpeed The current wind speed in the race. + * @param raceType The type of race this is. + * @param boatStatuses A list of BoatStatuses. One for each boat. + */ + public RaceStatus(byte messageVersionNumber, long currentTime, int raceID, RaceStatusEnum raceStatus, long expectedStartTime, int windDirection, int windSpeed, RaceTypeEnum raceType, List boatStatuses) { super(MessageType.RACESTATUS); + this.messageVersionNumber = messageVersionNumber; this.currentTime = currentTime; this.raceID = raceID; this.raceStatus = raceStatus; @@ -30,37 +92,58 @@ public class RaceStatus extends AC35Data { this.windDirection = windDirection; this.windSpeed = windSpeed; this.raceType = raceType; - this.boatStatuses = boatStatuses;//note this is not a copy so any alterations to the parent will affect this. + this.boatStatuses = boatStatuses; } + /** + * Returns the version number of this message. + * @return The version number of the message. + */ + public byte getMessageVersionNumber() { + return messageVersionNumber; + } - ///Getters. - + /** + * Returns the current time at which this message was generated. Milliseconds since unix epoch. + * @return Time this message was generated at. + */ public long getCurrentTime() { return currentTime; } + /** + * Returns the RaceID. + * @return The raceID. + */ public int getRaceID() { return raceID; } /** - * - * @return race status number + * Returns the race status. + * @return The current race status. */ - public byte getRaceStatus() + public RaceStatusEnum getRaceStatus() { return raceStatus; } + /** + * Returns the expected start time for the race. Milliseconds since unix epoch. + * @return Expected start time for the race. + */ public long getExpectedStartTime() { return expectedStartTime; } + /** + * Returns the current direction of the wind in the race. + * @return Current wind direction. + */ public int getWindDirection() { return windDirection; @@ -75,60 +158,70 @@ public class RaceStatus extends AC35Data { return windSpeed; } - public int getRaceType() + /** + * Retrusn the type of race this is. + * @return The type of race this is. + */ + public RaceTypeEnum getRaceType() { return raceType; } + /** + * Returns the list of BoatStatuses. One for each boat. + * @return List of BoatStatuses. + */ public List getBoatStatuses() { return boatStatuses; } + public boolean isNotActive() { - return raceStatus == 0; + return raceStatus == RaceStatusEnum.NOT_ACTIVE; } public boolean isWarning() { - return raceStatus == 1; + return raceStatus == RaceStatusEnum.WARNING; } public boolean isPreparatory() { - return raceStatus == 2; + return raceStatus == RaceStatusEnum.PREPARATORY; } public boolean isStarted() { - return raceStatus == 3; + return raceStatus == RaceStatusEnum.STARTED; } public boolean isFinished() { - return raceStatus == 4; + return raceStatus == RaceStatusEnum.FINISHED; } public boolean isRetired() { - return raceStatus == 5; + return raceStatus == RaceStatusEnum.RETIRED; } public boolean isAbandoned() { - return raceStatus == 6; + return raceStatus == RaceStatusEnum.ABANDONED; } public boolean isPostponed() { - return raceStatus == 7; + return raceStatus == RaceStatusEnum.POSTPONED; } public boolean isTerminated() { - return raceStatus == 8; + return raceStatus == RaceStatusEnum.TERMINATED; } public boolean isStartTimeSet() { - return raceStatus != 9; + return raceStatus != RaceStatusEnum.RACE_START_TIME_NOT_SET; } public boolean isPrestart() { - return raceStatus == 10; + return raceStatus == RaceStatusEnum.PRESTART; } + public double getScaledWindDirection() { return AC35UnitConverter.convertHeading(windDirection); } diff --git a/racevisionGame/src/main/java/visualiser/model/VisualiserRace.java b/racevisionGame/src/main/java/visualiser/model/VisualiserRace.java index 2969fbb8..fdc2802d 100644 --- a/racevisionGame/src/main/java/visualiser/model/VisualiserRace.java +++ b/racevisionGame/src/main/java/visualiser/model/VisualiserRace.java @@ -215,7 +215,7 @@ public class VisualiserRace extends Race implements Runnable { //Boat status. - BoatStatusEnum newBoatStatusEnum = BoatStatusEnum.fromByte(boatStatus.getBoatStatus()); + BoatStatusEnum newBoatStatusEnum = boatStatus.getBoatStatus(); //If we are changing from non-racing to racing, we need to initialise boat with their time at last mark. if ((boat.getStatus() != BoatStatusEnum.RACING) && (newBoatStatusEnum == BoatStatusEnum.RACING)) { @@ -309,7 +309,7 @@ public class VisualiserRace extends Race implements Runnable { private void updateRaceStatus(RaceStatus raceStatus) { //Race status enum. - this.raceStatusEnum = RaceStatusEnum.fromByte(raceStatus.getRaceStatus()); + this.raceStatusEnum = raceStatus.getRaceStatus(); //Wind. this.setWind( diff --git a/racevisionGame/src/test/java/network/MessageDecoders/BoatStatusDecoderTest.java b/racevisionGame/src/test/java/network/MessageDecoders/BoatStatusDecoderTest.java new file mode 100644 index 00000000..635e581c --- /dev/null +++ b/racevisionGame/src/test/java/network/MessageDecoders/BoatStatusDecoderTest.java @@ -0,0 +1,90 @@ +package network.MessageDecoders; + +import network.MessageEncoders.BoatStatusEncoder; +import network.MessageEncoders.RaceVisionByteEncoder; +import network.Messages.BoatStatus; +import network.Messages.Enums.BoatStatusEnum; +import org.junit.Assert; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +/** + * Test for the BoatStatus encoder and decoder + */ +public class BoatStatusDecoderTest { + + + /** + * Creates a BoatStatus message, encodes it, decodes it, and checks that the result matches the starting message. + * @throws Exception if test fails. + */ + @Test + public void boatStatusEncodeDecodeTest() throws Exception { + + long time = System.currentTimeMillis(); + + //Create data to serialize. + int boatSourceID = 5; + BoatStatusEnum boatStatusEnum = BoatStatusEnum.RACING; + byte boatLegNumber = 5; + byte boatPenaltiesAwarded = 4; + byte boatPenaltiesServed = 2; + long boatTimeAtNextMark = time + (1000 * 3); + long boatTimeAtFinish = boatTimeAtNextMark + (1000 * 15); + + BoatStatus boatStatus = new BoatStatus( + boatSourceID, + boatStatusEnum, + boatLegNumber, + boatPenaltiesAwarded, + boatPenaltiesServed, + boatTimeAtNextMark, + boatTimeAtFinish ); + + + BoatStatus boatStatusDecoded = encodeDecodeBoatStatus(boatStatus); + + compareBoatStatusMessages(boatStatus, boatStatusDecoded); + + } + + /** + * Encodes and decodes a BoatStatus, and returns it. + * @param boatStatus The BoatStatus to encode and decode. + * @return The decoded BoatStatus. + */ + private static BoatStatus encodeDecodeBoatStatus(BoatStatus boatStatus) { + + BoatStatusEncoder boatStatusEncoder = new BoatStatusEncoder(); + + byte[] boatStatusEncoded = boatStatusEncoder.encode(boatStatus); + + BoatStatusDecoder boatStatusDecoder = new BoatStatusDecoder(boatStatusEncoded); + + BoatStatus boatStatusDecoded = boatStatusDecoder.getMessage(); + + return boatStatusDecoded; + } + + + /** + * Compares two BoatStatus messages to check that they are equal. + * @param original The original BoatStatus message. + * @param decoded The decoded BoatStatus message. + */ + public static void compareBoatStatusMessages(BoatStatus original, BoatStatus decoded) { + + Assert.assertEquals(original.getSourceID(), decoded.getSourceID()); + Assert.assertEquals(original.getBoatStatus(), decoded.getBoatStatus()); + Assert.assertEquals(original.getLegNumber(), decoded.getLegNumber()); + Assert.assertEquals(original.getNumPenaltiesAwarded(), decoded.getNumPenaltiesAwarded()); + Assert.assertEquals(original.getNumPenaltiesServed(), decoded.getNumPenaltiesServed()); + Assert.assertEquals(original.getEstTimeAtNextMark(), decoded.getEstTimeAtNextMark()); + Assert.assertEquals(original.getEstTimeAtFinish(), decoded.getEstTimeAtFinish()); + + } + +} diff --git a/racevisionGame/src/test/java/network/MessageDecoders/RaceStatusDecoderTest.java b/racevisionGame/src/test/java/network/MessageDecoders/RaceStatusDecoderTest.java index a5600446..1adb69b4 100644 --- a/racevisionGame/src/test/java/network/MessageDecoders/RaceStatusDecoderTest.java +++ b/racevisionGame/src/test/java/network/MessageDecoders/RaceStatusDecoderTest.java @@ -1,27 +1,39 @@ package network.MessageDecoders; +import network.Exceptions.InvalidMessageException; import network.MessageEncoders.RaceVisionByteEncoder; import network.Messages.BoatStatus; +import network.Messages.Enums.BoatStatusEnum; +import network.Messages.Enums.RaceStatusEnum; +import network.Messages.Enums.RaceTypeEnum; import network.Messages.RaceStatus; import org.junit.Assert; import org.junit.Test; import java.util.ArrayList; +import java.util.Iterator; import java.util.List; /** - * Created by hba56 on 23/04/17. + * Test for the RaceStatus encoder and decoder */ public class RaceStatusDecoderTest { + + + /** + * Creates a RaceStatus message, encodes it, decodes it, and checks that the result matches the starting message. + * @throws Exception if test fails. + */ @Test - public void getByteArrayTest(){ + public void raceStatusEncodeDecodeTest() throws Exception { + long time = System.currentTimeMillis(); //Create data to serialize. int boat1SourceID = 5; int boat2SourceID = 8; - byte boat1Status = 2; - byte boat2Status = 2; + BoatStatusEnum boat1Status = BoatStatusEnum.RACING; + BoatStatusEnum boat2Status = BoatStatusEnum.RACING; byte boat1LegNumber = 5; byte boat2LegNumber = 3; byte boat1PenaltiesAwarded = 4; @@ -33,44 +45,85 @@ public class RaceStatusDecoderTest { long boat1TimeAtFinish = boat1TimeAtNextMark + (1000 * 15); long boat2TimeAtFinish = boat2TimeAtNextMark + (1000 * 7); - BoatStatus boatStatus1 = new BoatStatus(boat1SourceID, boat1Status, boat1LegNumber, boat1PenaltiesAwarded, boat1PenaltiesServed, boat1TimeAtNextMark, boat1TimeAtFinish); - BoatStatus boatStatus2 = new BoatStatus(boat2SourceID, boat2Status, boat2LegNumber, boat2PenaltiesAwarded, boat2PenaltiesServed, boat2TimeAtNextMark, boat2TimeAtFinish); + BoatStatus boatStatus1 = new BoatStatus( + boat1SourceID, + boat1Status, + boat1LegNumber, + boat1PenaltiesAwarded, + boat1PenaltiesServed, + boat1TimeAtNextMark, + boat1TimeAtFinish ); + + BoatStatus boatStatus2 = new BoatStatus( + boat2SourceID, + boat2Status, + boat2LegNumber, + boat2PenaltiesAwarded, + boat2PenaltiesServed, + boat2TimeAtNextMark, + boat2TimeAtFinish ); + int raceID = 585; - byte raceStatus = 3; + RaceStatusEnum raceStatus = RaceStatusEnum.STARTED; long raceStartTime = time - (1000 * 31); int windDirection = 2341; int windSpeed = 10201; - int raceType = 1; + RaceTypeEnum raceType = RaceTypeEnum.MATCH_RACE; List boatStatuses = new ArrayList<>(2); boatStatuses.add(boatStatus1); boatStatuses.add(boatStatus2); - RaceStatus raceStatusObject = new RaceStatus(time, raceID, raceStatus, raceStartTime, windDirection, windSpeed, raceType, boatStatuses); + RaceStatus raceStatusOriginal = new RaceStatus( + RaceStatus.currentMessageVersionNumber, + time, + raceID, + raceStatus, + raceStartTime, + windDirection, + windSpeed, + raceType, + boatStatuses ); - byte[] encodedRaceStatus = RaceVisionByteEncoder.raceStatus(raceStatusObject); + byte[] encodedRaceStatus = RaceVisionByteEncoder.encode(raceStatusOriginal); RaceStatusDecoder decoderTest = new RaceStatusDecoder(encodedRaceStatus); - Assert.assertEquals(0b10, decoderTest.getVersionNum()); - Assert.assertEquals(time, decoderTest.getTime()); - Assert.assertEquals(raceID, decoderTest.getRace()); - Assert.assertEquals(raceStatus, decoderTest.getRaceState()); - Assert.assertEquals(raceStartTime, decoderTest.getStartTime()); - Assert.assertEquals(windDirection, decoderTest.getRaceWindDir()); - Assert.assertEquals(windSpeed, decoderTest.getRaceWindSpeed()); + RaceStatus decodedMessage = decoderTest.getMessage(); + + compareRaceStatusMessages(raceStatusOriginal, decodedMessage); + + } + + + /** + * Compares two RaceStatus messages to check that they are equal. + * @param original The original RaceStatus message. + * @param decoded The decoded RaceStatus message. + */ + public static void compareRaceStatusMessages(RaceStatus original, RaceStatus decoded) { + + //Compare RaceStatus body. + Assert.assertEquals(original.getMessageVersionNumber(), decoded.getMessageVersionNumber()); + Assert.assertEquals(original.getCurrentTime(), decoded.getCurrentTime()); + Assert.assertEquals(original.getRaceID(), decoded.getRaceID()); + Assert.assertEquals(original.getRaceStatus(), decoded.getRaceStatus()); + Assert.assertEquals(original.getExpectedStartTime(), decoded.getExpectedStartTime()); + Assert.assertEquals(original.getWindDirection(), decoded.getWindDirection()); + Assert.assertEquals(original.getWindSpeed(), decoded.getWindSpeed()); + //Compare all BoatStatuses + Iterator originalIterator = original.getBoatStatuses().iterator(); + Iterator decodedIterator = decoded.getBoatStatuses().iterator(); - BoatStatus boat1 = decoderTest.getBoats().get(0); + while (originalIterator.hasNext() && decodedIterator.hasNext()) { - Assert.assertEquals(boat1SourceID, boat1.getSourceID()); - Assert.assertEquals(boat1Status, boat1.getBoatStatus()); - Assert.assertEquals(boat1LegNumber, boat1.getLegNumber()); - Assert.assertEquals(boat1PenaltiesAwarded, boat1.getNumPenaltiesAwarded()); - Assert.assertEquals(boat1PenaltiesServed, boat1.getNumPenaltiesServed()); - Assert.assertEquals(boat1TimeAtNextMark, boat1.getEstTimeAtNextMark()); - Assert.assertEquals(boat1TimeAtFinish, boat1.getEstTimeAtFinish()); + BoatStatusDecoderTest.compareBoatStatusMessages(originalIterator.next(), decodedIterator.next()); + + } } + + }