Merge branch 'master' into story_64_sails

# Conflicts:
#	racevisionGame/src/main/java/mock/app/MockOutput.java
#	racevisionGame/src/main/java/mock/model/MockRace.java
#	racevisionGame/src/main/java/mock/model/commandFactory/CommandFactory.java
#	racevisionGame/src/main/java/network/MessageDecoders/BoatActionDecoder.java
#	racevisionGame/src/main/java/network/MessageDecoders/BoatLocationDecoder.java
#	racevisionGame/src/main/java/visualiser/app/VisualiserInput.java
#	racevisionGame/src/main/java/visualiser/gameController/ControllerServer.java
#	racevisionGame/src/main/java/visualiser/model/ResizableRaceCanvas.java
#	racevisionGame/src/main/java/visualiser/model/VisualiserRace.java
#	racevisionGame/src/main/resources/visualiser/scenes/connect.fxml
#	racevisionGame/src/main/resources/visualiser/scenes/lobby.fxml
#	racevisionGame/src/test/java/mock/model/MockRaceTest.java
#	racevisionGame/src/test/java/network/Utils/AC35UnitConverterTest.java
#	racevisionGame/src/test/java/network/XMLMessageEncoderTest.java
This commit is contained in:
Joseph
2017-08-17 09:13:49 +12:00
184 changed files with 12652 additions and 4461 deletions
@@ -2,6 +2,7 @@ package network;
import network.Exceptions.InvalidMessageException;
import network.Exceptions.InvalidMessageTypeException;
import network.MessageDecoders.*;
import network.Messages.*;
import network.Messages.Enums.MessageType;
@@ -18,42 +19,71 @@ import static network.Utils.ByteConverter.bytesToLong;
*/
public class BinaryMessageDecoder {
///Length of the header.
/**
* Length of the header.
*/
private static final int headerLength = 15;
///Length of the CRC.
private static final int CRCLength = 4;//TODO these should probably be static defined somewhere else to be shared.
/**
* Length of the CRC.
*/
private static final int CRCLength = 4;
///The value the first sync byte should have.
/**
* The value the first sync byte should have.
*/
private static final byte syncByte1 = (byte) 0x47;
//The value the second sync byte should have.
/**
* The value the second sync byte should have.
*/
private static final byte syncByte2 = (byte) 0x83;
///The full message.
/**
* The full message.
*/
private byte[] fullMessage;
///The messageHeader.
/**
* The messageHeader.
*/
private byte[] messageHeader;
///The messageBody.
/**
* The messageBody.
*/
private byte[] messageBody;
///The sync bytes from the header..
/**
* The sync bytes from the header.
*/
private byte headerSync1;
private byte headerSync2;
///The message type from the header.
/**
* The message type from the header.
*/
private byte headerMessageType;
///The timestamp from the header.
/**
* The timestamp from the header.
*/
private long headerTimeStamp;
///The source ID from the header.
/**
* The source ID from the header.
*/
private int headerSourceID;
///The message body length from the header.
/**
* The message body length from the header.
*/
private int messageBodyLength;
///CRC value read from message header.
/**
* CRC value read from message header.
*/
private long messageCRCValue;
///Calculated CRC value from message.
/**
* Calculated CRC value from message.
*/
private long calculatedCRCValue;
@@ -115,96 +145,34 @@ public class BinaryMessageDecoder {
//Check the message body length.
throw new InvalidMessageException("MessageBody length in header does not equal the messageBody length. MessageBody length in header is: " + messageBodyLength + ", should be: " + messageBody.length);
}else if (headerSync1 != syncByte1) {
} else if (headerSync1 != syncByte1) {
//Check the first sync byte.
throw new InvalidMessageException("Sync byte 1 is wrong. Sync byte is: " + headerSync1 + ", should be: " + syncByte1);
}else if (headerSync2 != syncByte2) {
} else if (headerSync2 != syncByte2) {
//Check the second sync byte.
throw new InvalidMessageException("Sync byte 2 is wrong. Sync byte is: " + headerSync2 + ", should be: " + syncByte2);
}else if (calculatedCRCValue != messageCRCValue) {
} else if (calculatedCRCValue != messageCRCValue) {
//Check the CRC value.
throw new InvalidMessageException("CRC value is wrong. The calculated value is: " + calculatedCRCValue + ", should be: " + messageCRCValue);
}
//Now we create the message object based on what is actually in the message body.
MessageType mType = MessageType.fromByte(headerMessageType);
MessageType messageType = MessageType.fromByte(headerMessageType);
switch(mType) {
case HEARTBEAT:
//System.out.println("Decoding HeartBeat Message!");
//TODO maybe use HeartbeatDecoder.decode(message).
//TODO also, decoders for each message type should encapsulate the constructing of the object. E.g., return HeartbeatDecoder.decode(message);.
return new Heartbeat(bytesToLong(messageBody));
MessageDecoder decoder = null;
try {
decoder = DecoderFactory.create(messageType);
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());
case DISPLAYTEXTMESSAGE:
//System.out.println("Display Text Message");
//No decoder for this.
//throw new InvalidMessageException("Cannot decode DISPLAYTEXTMESSAGE - no decoder.");
case XMLMESSAGE:
//System.out.println("XML Message!");
XMLMessageDecoder xmdecoder = new XMLMessageDecoder(messageBody);
xmdecoder.decode();
return new XMLMessage(XMLMessage.currentVersionNumber, xmdecoder.getAckNumber(), xmdecoder.getTimeStamp(), xmdecoder.getXmlMsgSubType(), xmdecoder.getSequenceNumber(), xmdecoder.getXmlMessageContents());
case RACESTARTSTATUS:
//System.out.println("Race Start Status Message");
RaceStartStatusDecoder rssDecoder = new RaceStartStatusDecoder(messageBody);
return new RaceStartStatus(rssDecoder.getTime(), rssDecoder.getAck(), rssDecoder.getStartTime(), rssDecoder.getRaceID(), rssDecoder. getNotification());
case YACHTEVENTCODE:
//System.out.println("Yacht Action Code!");
//No decoder for this.
//throw new InvalidMessageException("Cannot decode YACHTEVENTCODE - no decoder.");
case YACHTACTIONCODE:
//System.out.println("Yacht Action Code!");
//No decoder for this.
//throw new InvalidMessageException("Cannot decode YACHTACTIONCODE - no decoder.");
case CHATTERTEXT:
//System.out.println("Chatter Text Message!");
//No decoder for this.
//throw new InvalidMessageException("Cannot decode CHATTERTEXT - no decoder.");
case BOATLOCATION:
//System.out.println("Boat Location Message!");
BoatLocationDecoder blDecoder = new BoatLocationDecoder(messageBody);
return blDecoder.getMessage();
case MARKROUNDING:
//System.out.println("Mark Rounding Message!");
MarkRoundingDecoder mrDecoder = new MarkRoundingDecoder(messageBody);
return mrDecoder.getMarkRounding();
case COURSEWIND:
//System.out.println("Course Wind Message!");
CourseWindDecoder cwDecoder = new CourseWindDecoder(messageBody);
return new CourseWinds(cwDecoder.getMessageVersionNumber(), cwDecoder.getByteWindID(), cwDecoder.getLoopMessages());
case AVGWIND:
//System.out.println("Average Wind Message!");
AverageWindDecoder awDecoder = new AverageWindDecoder(messageBody);
return awDecoder.getAverageWind();
case BOATACTION:
BoatActionDecoder baDecoder = new BoatActionDecoder(messageBody);
return new BoatAction(baDecoder.getBoatAction());
default:
//System.out.println("Broken Message!");
//throw new InvalidMessageException("Broken message! Did not recognise message type: " + headerMessageType + ".");
return null;
} catch (InvalidMessageTypeException e) {
throw new InvalidMessageException("Could not create decoder for MessageType: " + messageType, e);
}
return decoder.decode(messageBody);
}
@@ -0,0 +1,17 @@
package network.Exceptions;
/**
* An exception thrown when we encounter a message type that isn't recognised.
*/
public class InvalidMessageTypeException extends Exception {
public InvalidMessageTypeException(String message) {
super(message);
}
public InvalidMessageTypeException(String message, Throwable cause) {
super(message, cause);
}
}
@@ -0,0 +1,9 @@
package network.MessageControllers;
public class MessageController {
}
@@ -1,56 +1,107 @@
package network.MessageDecoders;
import network.Exceptions.InvalidMessageException;
import network.Messages.AC35Data;
import network.Messages.AverageWind;
import network.Utils.ByteConverter;
import static network.Utils.AC35UnitConverter.*;
import java.util.Arrays;
/**
* Created by hba56 on 23/04/17.
* Decodes {@link AverageWind} messages.
*/
public class AverageWindDecoder {
byte messageVersionNumber;
byte[] byteTime;
byte[] byteRawPeriod;
byte[] byteRawSpeed;
byte[] bytePeriod2;
byte[] byteSpeed2;
byte[] bytePeriod3;
byte[] byteSpeed3;
byte[] bytePeriod4;
byte[] byteSpeed4;
public class AverageWindDecoder implements MessageDecoder {
AverageWind averageWind;
/**
* The encoded message.
*/
private byte[] encodedMessage;
public AverageWindDecoder(byte[] encodedAverageWind) {
messageVersionNumber = encodedAverageWind[0];
byteTime = Arrays.copyOfRange(encodedAverageWind, 1, 7);
byteRawPeriod = Arrays.copyOfRange(encodedAverageWind, 7, 9);
byteRawSpeed = Arrays.copyOfRange(encodedAverageWind, 9, 11);
bytePeriod2 = Arrays.copyOfRange(encodedAverageWind, 11, 13);
byteSpeed2 = Arrays.copyOfRange(encodedAverageWind, 13, 15);
bytePeriod3 = Arrays.copyOfRange(encodedAverageWind, 15, 17);
byteSpeed3 = Arrays.copyOfRange(encodedAverageWind, 17, 19);
bytePeriod4 = Arrays.copyOfRange(encodedAverageWind, 19, 21);
byteSpeed4 = Arrays.copyOfRange(encodedAverageWind, 21, 23);
/**
* The decoded message.
*/
private AverageWind message;
int msgNum = ByteConverter.bytesToInt(messageVersionNumber);
long lngTime = ByteConverter.bytesToLong(byteTime);
int intRawPeriod = ByteConverter.bytesToInt(byteRawPeriod);
int intRawSpeed = ByteConverter.bytesToInt(byteRawSpeed);
int intPeriod2 = ByteConverter.bytesToInt(bytePeriod2);
int intSpeed2 = ByteConverter.bytesToInt(byteSpeed2);
int intPeriod3 = ByteConverter.bytesToInt(bytePeriod3);
int intSpeed3 = ByteConverter.bytesToInt(byteSpeed3);
int intPeriod4 = ByteConverter.bytesToInt(bytePeriod4);
int intSpeed4 = ByteConverter.bytesToInt(byteSpeed4);
this.averageWind = new AverageWind(msgNum, lngTime, intRawPeriod, intRawSpeed, intPeriod2, intSpeed2, intPeriod3, intSpeed3, intPeriod4, intSpeed4);
public AverageWindDecoder() {
}
@Override
public AC35Data decode(byte[] encodedMessage) throws InvalidMessageException {
this.encodedMessage = encodedMessage;
try {
byte messageVersionNumber = encodedMessage[0];
byte[] byteTime = Arrays.copyOfRange(encodedMessage, 1, 7);
long time = ByteConverter.bytesToLong(byteTime);
byte[] byteRawPeriod = Arrays.copyOfRange(encodedMessage, 7, 9);
int intRawPeriod = ByteConverter.bytesToInt(byteRawPeriod);
long rawPeriod = unpackAverageWindPeriod(intRawPeriod);
byte[] byteRawSpeed = Arrays.copyOfRange(encodedMessage, 9, 11);
int intRawSpeed = ByteConverter.bytesToInt(byteRawSpeed);
double rawSpeedKnots = unpackMMperSecToKnots(intRawSpeed);
byte[] bytePeriod2 = Arrays.copyOfRange(encodedMessage, 11, 13);
int intPeriod2 = ByteConverter.bytesToInt(bytePeriod2);
long period2 = unpackAverageWindPeriod(intPeriod2);
byte[] byteSpeed2 = Arrays.copyOfRange(encodedMessage, 13, 15);
int intSpeed2 = ByteConverter.bytesToInt(byteSpeed2);
double speed2Knots = unpackMMperSecToKnots(intSpeed2);
byte[] bytePeriod3 = Arrays.copyOfRange(encodedMessage, 15, 17);
int intPeriod3 = ByteConverter.bytesToInt(bytePeriod3);
long period3 = unpackAverageWindPeriod(intPeriod3);
byte[] byteSpeed3 = Arrays.copyOfRange(encodedMessage, 17, 19);
int intSpeed3 = ByteConverter.bytesToInt(byteSpeed3);
double speed3Knots = unpackMMperSecToKnots(intSpeed3);
byte[] bytePeriod4 = Arrays.copyOfRange(encodedMessage, 19, 21);
int intPeriod4 = ByteConverter.bytesToInt(bytePeriod4);
long period4 = unpackAverageWindPeriod(intPeriod4);
byte[] byteSpeed4 = Arrays.copyOfRange(encodedMessage, 21, 23);
int intSpeed4 = ByteConverter.bytesToInt(byteSpeed4);
double speed4Knots = unpackMMperSecToKnots(intSpeed4);
message = new AverageWind(
messageVersionNumber,
time,
rawPeriod,
rawSpeedKnots,
period2,
speed2Knots,
period3,
speed3Knots,
period4,
speed4Knots);
return message;
} catch (Exception e) {
throw new InvalidMessageException("Could not decode AverageWind message.", e);
}
}
public AverageWind getAverageWind() {
return averageWind;
/**
* Returns the decoded message.
* @return The decoded message.
*/
public AverageWind getMessage() {
return message;
}
}
@@ -1,18 +1,56 @@
package network.MessageDecoders;
import network.Exceptions.InvalidMessageException;
import network.Messages.AC35Data;
import network.Messages.BoatAction;
import network.Messages.Enums.BoatActionEnum;
public class BoatActionDecoder {
byte byteBoatAction;
BoatActionEnum boatAction;
import java.util.Arrays;
public BoatActionDecoder(byte[] encodedBoatAction) {
byteBoatAction = encodedBoatAction[0];
/**
* Decodes {@link BoatAction} messages.
*/
public class BoatActionDecoder implements MessageDecoder {
boatAction = BoatActionEnum.fromByte(byteBoatAction);
/**
* The encoded message.
*/
private byte[] encodedMessage;
/**
* The decoded message.
*/
private BoatAction message;
/**
* Constructs a decoder to decode a given message.
*/
public BoatActionDecoder() {
}
public BoatActionEnum getBoatAction() {
return boatAction;
@Override
public AC35Data decode(byte[] encodedMessage) throws InvalidMessageException {
this.encodedMessage = encodedMessage;
try {
BoatActionEnum boatActionEnum = BoatActionEnum.fromByte(encodedMessage[0]);
message = new BoatAction(boatActionEnum);
return message;
} catch (Exception e) {
throw new InvalidMessageException("Could not decode BoatAction message.", e);
}
}
}
/**
* Returns the decoded message.
* @return The decoded message.
*/
public BoatAction getMessage() {
return message;
}
}
@@ -1,140 +1,168 @@
package network.MessageDecoders;
import network.Exceptions.InvalidMessageException;
import network.Messages.AC35Data;
import network.Messages.BoatLocation;
import network.Messages.Enums.BoatLocationDeviceEnum;
import shared.model.Azimuth;
import shared.model.Bearing;
import java.util.Arrays;
import static network.Utils.AC35UnitConverter.*;
import static network.Utils.ByteConverter.*;
/**
* Created by hba56 on 21/04/17.
* Decodes {@link BoatLocation} messages.
*/
public class BoatLocationDecoder {
private byte messageVersionNumber;
private byte[] time;
private byte[] sourceID;
private byte[] seqNum;
private byte deviceType;
private byte[] latitude;
private byte[] longitude;
private byte[] altitude;
private byte[] heading;
private byte[] pitch;
private byte[] roll;
private byte[] boatSpeed;
private byte[] cog;
private byte[] sog;
private byte[] apparentWindSpeed;
private byte[] apparentWindAngle;
private byte[] trueWindSpeed;
private byte[] trueWindDirection;
private byte[] trueWindAngle;
private byte[] currentDrift;
private byte[] currentSet;
private byte[] rudderAngle;
public class BoatLocationDecoder implements MessageDecoder {
/**
* The encoded message.
*/
private byte[] encodedMessage;
/**
* The decoded message.
*/
private BoatLocation message;
public BoatLocationDecoder(byte[] encodedBoatLocation) {
byte numMessageVersionNumber = 0;
long numTime = 0;
int numSourceID = 0;
int numSeqNum = 0;
byte numDeviceType = 0;
int numLatitude = 0;
int numLongitude = 0;
int numAltitude = 0;
int numHeading = 0;
short numPitch = 0;
short numRoll = 0;
int numBoatSpeed = 0;
int numCog = 0;
int numSog = 0;
int numApparentWindSpeed = 0;
short numApparentWindAngle = 0;
int numTrueWindSpeed = 0;
short numTrueWindDirection = 0;
short numTrueWindAngle = 0;
int numCurrentDrift = 0;
int numCurrentSet = 0;
short numRudderAngle = 0;
try {
messageVersionNumber = encodedBoatLocation[0];
numMessageVersionNumber = messageVersionNumber;
time = Arrays.copyOfRange(encodedBoatLocation, 1, 7);
numTime = bytesToLong(time);
sourceID = Arrays.copyOfRange(encodedBoatLocation, 7, 11);
numSourceID = bytesToInt(sourceID);
seqNum = Arrays.copyOfRange(encodedBoatLocation, 11, 15);
numSeqNum = bytesToInt(seqNum);
deviceType = encodedBoatLocation[15];
numDeviceType = deviceType;
latitude = Arrays.copyOfRange(encodedBoatLocation, 16, 20);
numLatitude = bytesToInt(latitude);
longitude = Arrays.copyOfRange(encodedBoatLocation, 20, 24);
numLongitude = bytesToInt(longitude);
altitude = Arrays.copyOfRange(encodedBoatLocation, 24, 28);
numAltitude = bytesToInt(altitude);
heading = Arrays.copyOfRange(encodedBoatLocation, 28, 30);
numHeading = bytesToInt(heading);
pitch = Arrays.copyOfRange(encodedBoatLocation, 30, 32);
numPitch = bytesToShort(pitch);
roll = Arrays.copyOfRange(encodedBoatLocation, 32, 34);
numRoll = bytesToShort(roll);
boatSpeed = Arrays.copyOfRange(encodedBoatLocation, 34, 36);
numBoatSpeed = bytesToInt(boatSpeed);
cog = Arrays.copyOfRange(encodedBoatLocation, 36, 38);
numCog = bytesToInt(cog);
sog = Arrays.copyOfRange(encodedBoatLocation, 38, 40);
numSog = bytesToInt(sog);
apparentWindSpeed = Arrays.copyOfRange(encodedBoatLocation, 40, 42);
numApparentWindSpeed = bytesToInt(apparentWindSpeed);
apparentWindAngle = Arrays.copyOfRange(encodedBoatLocation, 42, 44);
numApparentWindAngle = bytesToShort(apparentWindAngle);
trueWindSpeed = Arrays.copyOfRange(encodedBoatLocation, 44, 46);
numTrueWindSpeed = bytesToInt(trueWindSpeed);
trueWindDirection = Arrays.copyOfRange(encodedBoatLocation, 46, 48);
numTrueWindDirection = bytesToShort(trueWindDirection);
trueWindAngle = Arrays.copyOfRange(encodedBoatLocation, 48, 50);
numTrueWindAngle = bytesToShort(trueWindAngle);
currentDrift = Arrays.copyOfRange(encodedBoatLocation, 50, 52);
numCurrentDrift = bytesToInt(currentDrift);
currentSet = Arrays.copyOfRange(encodedBoatLocation, 52, 54);
numCurrentSet = bytesToShort(currentSet);
rudderAngle = Arrays.copyOfRange(encodedBoatLocation, 54, 56);
numRudderAngle = bytesToShort(rudderAngle);
} catch(ArrayIndexOutOfBoundsException e){
}
message = new BoatLocation(numMessageVersionNumber, numTime,
numSourceID, numSeqNum, numDeviceType, numLatitude,
numLongitude, numAltitude, numHeading, numPitch,
numRoll, numBoatSpeed, numCog, numSog, numApparentWindSpeed,
numApparentWindAngle, numTrueWindSpeed, numTrueWindDirection,
numTrueWindAngle, numCurrentDrift, numCurrentSet, numRudderAngle
);/*
message = new BoatLocation(messageVersionNumber, bytesToLong(time),
bytesToInt(sourceID), bytesToInt(seqNum),
deviceType, bytesToInt(latitude),
bytesToInt(longitude), bytesToInt(altitude),
bytesToInt(heading), bytesToShort(pitch),
bytesToShort(roll), bytesToInt(boatSpeed),
bytesToInt(cog), bytesToInt(sog),
bytesToInt(apparentWindSpeed), bytesToShort(apparentWindAngle),
bytesToInt(trueWindSpeed), bytesToShort(trueWindDirection),
bytesToShort(trueWindAngle), bytesToInt(currentDrift),
bytesToInt(currentSet), bytesToShort(rudderAngle)
);*/
// System.out.println(bytesToInt(sourceID));
// System.out.println(bytesToInt(boatSpeed));
/**
* Constructs a decoder to decode a given message.
*/
public BoatLocationDecoder() {
}
@Override
public AC35Data decode(byte[] encodedMessage) throws InvalidMessageException {
this.encodedMessage = encodedMessage;
try {
byte[] messageVersionNumberBytes = Arrays.copyOfRange(encodedMessage, 0, 1);
byte messageVersionNumber = messageVersionNumberBytes[0];
byte[] timeBytes = Arrays.copyOfRange(encodedMessage, 1, 7);
long time = bytesToLong(timeBytes);
byte[] sourceIDBytes = Arrays.copyOfRange(encodedMessage, 7, 11);
int sourceID = bytesToInt(sourceIDBytes);
byte[] seqNumBytes = Arrays.copyOfRange(encodedMessage, 11, 15);
int seqNum = bytesToInt(seqNumBytes);
byte[] deviceTypeBytes = Arrays.copyOfRange(encodedMessage, 15, 16);
BoatLocationDeviceEnum deviceType = BoatLocationDeviceEnum.fromByte(deviceTypeBytes[0]);
byte[] latitudeBytes = Arrays.copyOfRange(encodedMessage, 16, 20);
int numLatitude = bytesToInt(latitudeBytes);
double latitude = unpackGPS(numLatitude);
byte[] longitudeBytes = Arrays.copyOfRange(encodedMessage, 20, 24);
int numLongitude = bytesToInt(longitudeBytes);
double longitude = unpackGPS(numLongitude);
byte[] altitudeBytes = Arrays.copyOfRange(encodedMessage, 24, 28);
int numAltitude = bytesToInt(altitudeBytes);
byte[] headingBytes = Arrays.copyOfRange(encodedMessage, 28, 30);
int numHeading = bytesToInt(headingBytes);
Bearing heading = Bearing.fromDegrees(unpackHeading(numHeading));
byte[] pitchBytes = Arrays.copyOfRange(encodedMessage, 30, 32);
short numPitch = bytesToShort(pitchBytes);
byte[] rollBytes = Arrays.copyOfRange(encodedMessage, 32, 34);
short numRoll = bytesToShort(rollBytes);
byte[] boatSpeedBytes = Arrays.copyOfRange(encodedMessage, 34, 36);
int numBoatSpeed = bytesToInt(boatSpeedBytes);
double boatSpeedKnots = unpackMMperSecToKnots(numBoatSpeed);
byte[] cogBytes = Arrays.copyOfRange(encodedMessage, 36, 38);
int numCog = bytesToInt(cogBytes);
Bearing cog = Bearing.fromDegrees(unpackHeading(numCog));
byte[] sogBytes = Arrays.copyOfRange(encodedMessage, 38, 40);
int numSog = bytesToInt(sogBytes);
double sogKnots = unpackMMperSecToKnots(numSog);
byte[] apparentWindSpeedBytes = Arrays.copyOfRange(encodedMessage, 40, 42);
int numApparentWindSpeed = bytesToInt(apparentWindSpeedBytes);
double apparentWindSpeedKnots = unpackMMperSecToKnots(numApparentWindSpeed);
byte[] apparentWindAngleBytes = Arrays.copyOfRange(encodedMessage, 42, 44);
short numApparentWindAngle = bytesToShort(apparentWindAngleBytes);
Azimuth apparentWindAngle = Azimuth.fromDegrees(unpackTrueWindAngle(numApparentWindAngle));
byte[] trueWindSpeedBytes = Arrays.copyOfRange(encodedMessage, 44, 46);
int numTrueWindSpeed = bytesToInt(trueWindSpeedBytes);
double trueWindSpeedKnots = unpackMMperSecToKnots(numTrueWindSpeed);
byte[] trueWindDirectionBytes = Arrays.copyOfRange(encodedMessage, 46, 48);
short numTrueWindDirection = bytesToShort(trueWindDirectionBytes);
Bearing trueWindDirection = Bearing.fromDegrees(unpackHeading(numTrueWindDirection));
byte[] trueWindAngleBytes = Arrays.copyOfRange(encodedMessage, 48, 50);
short numTrueWindAngle = bytesToShort(trueWindAngleBytes);
Azimuth trueWindAngle = Azimuth.fromDegrees(unpackTrueWindAngle(numTrueWindAngle));
byte[] currentDriftBytes = Arrays.copyOfRange(encodedMessage, 50, 52);
int numCurrentDrift = bytesToInt(currentDriftBytes);
double currentDriftKnots = unpackMMperSecToKnots(numCurrentDrift);
byte[] currentSetBytes = Arrays.copyOfRange(encodedMessage, 52, 54);
int numCurrentSet = bytesToShort(currentSetBytes);
Bearing currentSet = Bearing.fromDegrees(unpackHeading(numCurrentSet));
byte[] rudderAngleBytes = Arrays.copyOfRange(encodedMessage, 54, 56);
short numRudderAngle = bytesToShort(rudderAngleBytes);
Azimuth rudderAngle = Azimuth.fromDegrees(unpackTrueWindAngle(numRudderAngle));
message = new BoatLocation(
messageVersionNumber,
time,
sourceID,
seqNum,
deviceType,
latitude,
longitude,
numAltitude,
heading,
numPitch,
numRoll,
boatSpeedKnots,
cog,
sogKnots,
apparentWindSpeedKnots,
apparentWindAngle,
trueWindSpeedKnots,
trueWindDirection,
trueWindAngle,
currentDriftKnots,
currentSet,
rudderAngle);
return message;
} catch (Exception e) {
throw new InvalidMessageException("Could not decode BoatLocation message.", e);
}
}
/**
* Returns the decoded message.
* @return The decoded message.
*/
public BoatLocation getMessage() {
return message;
}
@@ -0,0 +1,98 @@
package network.MessageDecoders;
import network.Exceptions.InvalidMessageException;
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.
*/
public BoatStatusDecoder() {
}
/**
* Decodes the contained message.
* @param encodedMessage The message to decode.
* @return The decoded message.
* @throws InvalidMessageException Thrown if the encoded message is invalid in some way, or cannot be decoded.
*/
public BoatStatus decode(byte[] encodedMessage) throws InvalidMessageException {
this.encodedMessage = encodedMessage;
try {
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 );
return message;
} catch (Exception e) {
throw new InvalidMessageException("Could not decode BoatStatus message.", e);
}
}
/**
* Returns the decoded message.
* @return The decoded message.
*/
public BoatStatus getMessage() {
return message;
}
}
@@ -1,64 +1,102 @@
package network.MessageDecoders;
import network.Exceptions.InvalidMessageException;
import network.Messages.CourseWind;
import shared.model.Bearing;
import java.util.ArrayList;
import java.util.Arrays;
import static network.Utils.ByteConverter.bytesToInt;
import static network.Utils.ByteConverter.bytesToLong;
import static network.Utils.AC35UnitConverter.*;
import static network.Utils.ByteConverter.*;
/**
* Created by hba56 on 23/04/17.
* Decodes {@link CourseWind} messages.
*/
public class CourseWindDecoder {
byte messageVersionNumber;
byte byteWindID;
byte loopCount;
ArrayList<CourseWind> loopMessages = new ArrayList();
public CourseWindDecoder(byte[] encodedCourseWind) {
final int lengthInBytesOfMessages = 20;
/**
* The encoded message.
*/
private byte[] encodedMessage;
messageVersionNumber = encodedCourseWind[0];
byteWindID = encodedCourseWind[1];
loopCount = encodedCourseWind[2];
byte[] loopMessagesBytes = Arrays.copyOfRange(encodedCourseWind, 3, lengthInBytesOfMessages*loopCount+3);
int messageLoopIndex = 0;
/**
* The decoded message.
*/
private CourseWind message;
for (int i=0; i < loopCount; i++) {
byte[] messageBytes = Arrays.copyOfRange(loopMessagesBytes, messageLoopIndex, messageLoopIndex+20);
ArrayList test = new ArrayList();
byte[] windId = Arrays.copyOfRange(messageBytes, 0, 1);
byte[] time = Arrays.copyOfRange(messageBytes, 1, 7);
byte[] raceID = Arrays.copyOfRange(messageBytes, 7, 11);
byte[] windDirection = Arrays.copyOfRange(messageBytes, 11, 13);
byte[] windSpeed = Arrays.copyOfRange(messageBytes, 13, 15);
byte[] bestUpwindAngle = Arrays.copyOfRange(messageBytes, 15, 17);
byte[] bestDownwindAngle = Arrays.copyOfRange(messageBytes, 17, 19);
byte[] flags = Arrays.copyOfRange(messageBytes, 19, 20);
CourseWind message = new CourseWind(windId[0], bytesToLong(time),
bytesToInt(raceID), bytesToInt(windDirection),
bytesToInt(windSpeed), bytesToInt(bestUpwindAngle),
bytesToInt(bestDownwindAngle), flags[0]);
loopMessages.add(message);
messageLoopIndex += 20;
/**
* Constructs a decoder to decode a given message.
*/
public CourseWindDecoder() {
}
/**
* Decodes the contained message.
* @param encodedMessage The message to decode.
* @return The decoded message.
* @throws InvalidMessageException Thrown if the encoded message is invalid in some way, or cannot be decoded.
*/
public CourseWind decode(byte[] encodedMessage) throws InvalidMessageException {
this.encodedMessage = encodedMessage;
try {
byte[] windId = Arrays.copyOfRange(encodedMessage, 0, 1);
byte[] timeBytes = Arrays.copyOfRange(encodedMessage, 1, 7);
long time = bytesToLong(timeBytes);
byte[] raceIDBytes = Arrays.copyOfRange(encodedMessage, 7, 11);
int raceIDInt = bytesToInt(raceIDBytes);
byte[] windDirectionBytes = Arrays.copyOfRange(encodedMessage, 11, 13);
int windDirectionInt = bytesToInt(windDirectionBytes);
Bearing windDirection = Bearing.fromDegrees(unpackHeading(windDirectionInt));
byte[] windSpeedBytes = Arrays.copyOfRange(encodedMessage, 13, 15);
int windSpeedInt = bytesToInt(windSpeedBytes);
double windSpeedKnots = unpackMMperSecToKnots(windSpeedInt);
byte[] bestUpwindAngleBytes = Arrays.copyOfRange(encodedMessage, 15, 17);
int bestUpwindAngleInt = bytesToInt(bestUpwindAngleBytes);
Bearing bestUpwindAngle = Bearing.fromDegrees(unpackHeading(bestUpwindAngleInt));
byte[] bestDownwindAngleBytes = Arrays.copyOfRange(encodedMessage, 17, 19);
int bestDownwindAngleInt = bytesToInt(bestDownwindAngleBytes);
Bearing bestDownwindAngle = Bearing.fromDegrees(unpackHeading(bestDownwindAngleInt));
byte[] flags = Arrays.copyOfRange(encodedMessage, 19, 20);
message = new CourseWind(
windId[0],
time,
raceIDInt,
windDirection,
windSpeedKnots,
bestUpwindAngle,
bestDownwindAngle,
flags[0]);
return message;
} catch (Exception e) {
throw new InvalidMessageException("Could not decode CourseWind message.", e);
}
}
public ArrayList<CourseWind> getLoopMessages() {
return loopMessages;
/**
* Returns the decoded message.
* @return The decoded message.
*/
public CourseWind getMessage() {
return message;
}
public byte getMessageVersionNumber() {
return messageVersionNumber;
}
public byte getByteWindID() {
return byteWindID;
}
}
@@ -0,0 +1,93 @@
package network.MessageDecoders;
import network.Exceptions.InvalidMessageException;
import network.Messages.AC35Data;
import network.Messages.CourseWind;
import network.Messages.CourseWinds;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import static network.Utils.ByteConverter.bytesToInt;
import static network.Utils.ByteConverter.bytesToLong;
/**
* Decodes {@link CourseWinds} messages.
*/
public class CourseWindsDecoder implements MessageDecoder {
/**
* The encoded message.
*/
private byte[] encodedMessage;
/**
* The decoded message.
*/
private CourseWinds message;
/**
* Constructs a decoder to decode a given message.
*/
public CourseWindsDecoder() {
}
@Override
public AC35Data decode(byte[] encodedMessage) throws InvalidMessageException {
this.encodedMessage = encodedMessage;
try {
//The header is three bytes.
byte messageVersionNumber = encodedMessage[0];
byte byteWindID = encodedMessage[1];
byte loopCount = encodedMessage[2];
//A CourseWind object is 20 bytes.
final int courseWindByteLength = 20;
List<CourseWind> loopMessages = new ArrayList();
//The header is 3 bytes, so we need the remaining bytes.
byte[] loopMessagesBytes = Arrays.copyOfRange(encodedMessage, 3, courseWindByteLength * loopCount + 3);
for (int messageLoopIndex = 0; messageLoopIndex < (loopCount * courseWindByteLength); messageLoopIndex += courseWindByteLength) {
byte[] messageBytes = Arrays.copyOfRange(loopMessagesBytes, messageLoopIndex, messageLoopIndex + courseWindByteLength);
CourseWindDecoder courseWindDecoder = new CourseWindDecoder();
CourseWind courseWind = courseWindDecoder.decode(messageBytes);
loopMessages.add(courseWind);
}
message = new CourseWinds(
messageVersionNumber,
byteWindID,
loopMessages);
return message;
} catch (Exception e) {
throw new InvalidMessageException("Could not decode CourseWinds message.", e);
}
}
/**
* Returns the decoded message.
* @return The decoded message.
*/
public CourseWinds getMessage() {
return message;
}
}
@@ -0,0 +1,70 @@
package network.MessageDecoders;
import network.Exceptions.InvalidMessageTypeException;
import network.Messages.Enums.MessageType;
/**
* Factory to create the appropriate decoder for a given message.
*/
public class DecoderFactory {
/**
* Private constructor. Currently doesn't need to be constructed.
*/
private DecoderFactory(){
}
/**
* Creates the correct type of decoder for a given message type.
* @param type Type of message you want a decoder for.
* @return The decoder.
* @throws InvalidMessageTypeException If you pass in a {@link MessageType} that isn't recognised.
*/
public static MessageDecoder create(MessageType type) throws InvalidMessageTypeException {
switch (type) {
case HEARTBEAT: return new HeartBeatDecoder();
case RACESTATUS: return new RaceStatusDecoder();
//case DISPLAYTEXTMESSAGE: return new DisplayTextMessageDecoder();//TODO
case XMLMESSAGE: return new XMLMessageDecoder();
case RACESTARTSTATUS: return new RaceStartStatusDecoder();
//case YACHTEVENTCODE: return new YachtEventCodeDecoder();//TODO
//case YACHTACTIONCODE: return new YachtActionCodeDecoder();//TODO
//case CHATTERTEXT: return new ChatterTextDecoder();//TODO
case BOATLOCATION: return new BoatLocationDecoder();
case MARKROUNDING: return new MarkRoundingDecoder();
case COURSEWIND: return new CourseWindsDecoder();
case AVGWIND: return new AverageWindDecoder();
case REQUEST_TO_JOIN: return new RequestToJoinDecoder();
case JOIN_ACCEPTANCE: return new JoinAcceptanceDecoder();
case BOATACTION: return new BoatActionDecoder();
default: throw new InvalidMessageTypeException("Unrecognised message type: " + type);
}
}
}
@@ -0,0 +1,57 @@
package network.MessageDecoders;
import network.Exceptions.InvalidMessageException;
import network.Messages.AC35Data;
import network.Messages.Enums.BoatActionEnum;
import network.Messages.HeartBeat;
import static network.Utils.ByteConverter.bytesToLong;
/**
* Decodes {@link network.Messages.HeartBeat} messages.
*/
public class HeartBeatDecoder implements MessageDecoder {
/**
* The encoded message.
*/
private byte[] encodedMessage;
/**
* The decoded message.
*/
private HeartBeat message;
/**
* Constructs a decoder to decode a given message.
*/
public HeartBeatDecoder() {
}
@Override
public AC35Data decode(byte[] encodedMessage) throws InvalidMessageException {
this.encodedMessage = encodedMessage;
try {
message = new HeartBeat(bytesToLong(encodedMessage));
return message;
} catch (Exception e) {
throw new InvalidMessageException("Could not decode HeartBeat message.", e);
}
}
/**
* Returns the decoded message.
* @return The decoded message.
*/
public HeartBeat getMessage() {
return message;
}
}
@@ -0,0 +1,74 @@
package network.MessageDecoders;
import network.Exceptions.InvalidMessageException;
import network.Messages.AC35Data;
import network.Messages.Enums.JoinAcceptanceEnum;
import network.Messages.JoinAcceptance;
import network.Utils.ByteConverter;
import java.util.Arrays;
/**
* Decoder for {@link JoinAcceptance} messages.
*/
public class JoinAcceptanceDecoder implements MessageDecoder {
/**
* The encoded message.
*/
private byte[] encodedMessage;
/**
* The decoded message.
*/
private JoinAcceptance message;
/**
* Constructs a decoder to decode a given message.
*/
public JoinAcceptanceDecoder() {
}
@Override
public AC35Data decode(byte[] encodedMessage) throws InvalidMessageException {
this.encodedMessage = encodedMessage;
try {
//SourceID is first four bytes.
byte[] sourceIdBytes = Arrays.copyOfRange(encodedMessage, 0, 4);
//Next byte is acceptance type.
byte[] acceptanceBytes = Arrays.copyOfRange(encodedMessage, 4, 5);
//SourceID is an int.
int sourceID = ByteConverter.bytesToInt(sourceIdBytes);
//Acceptance enum is a byte.
JoinAcceptanceEnum acceptanceType = JoinAcceptanceEnum.fromByte(acceptanceBytes[0]);
message = new JoinAcceptance(acceptanceType, sourceID);
return message;
} catch (Exception e) {
throw new InvalidMessageException("Could not decode JoinAcceptance message.", e);
}
}
/**
* Returns the decoded message.
* @return The decoded message.
*/
public JoinAcceptance getMessage() {
return message;
}
}
@@ -1,52 +1,97 @@
package network.MessageDecoders;
import network.Exceptions.InvalidMessageException;
import network.Messages.AC35Data;
import network.Messages.Enums.MarkRoundingBoatStatusEnum;
import network.Messages.Enums.MarkRoundingSideEnum;
import network.Messages.Enums.MarkRoundingTypeEnum;
import network.Messages.MarkRounding;
import network.Utils.ByteConverter;
import java.util.Arrays;
/**
* Created by hba56 on 23/04/17.
* Decoder for {@link MarkRounding} messages.
*/
public class MarkRoundingDecoder {
byte messageVersionNumber;
byte[] byteTime;
byte[] byteAck;
byte[] byteRaceID;
byte[] byteSourceID;
byte byteBoatStatus;
byte byteRoundingSide;
byte byteMarkType;
byte byteMarkID;
public class MarkRoundingDecoder implements MessageDecoder {
MarkRounding markRounding;
/**
* The encoded message.
*/
private byte[] encodedMessage;
public MarkRoundingDecoder(byte[] encodedMarkRounding) {
messageVersionNumber = encodedMarkRounding[0];
byteTime = Arrays.copyOfRange(encodedMarkRounding, 1, 7);
byteAck = Arrays.copyOfRange(encodedMarkRounding, 7, 9);
byteRaceID = Arrays.copyOfRange(encodedMarkRounding, 9, 13);
byteSourceID = Arrays.copyOfRange(encodedMarkRounding, 13, 17);
byteBoatStatus = encodedMarkRounding[17];
byteRoundingSide = encodedMarkRounding[18];
byteMarkType = encodedMarkRounding[19];
byteMarkID = encodedMarkRounding[20];
/**
* The decoded message.
*/
private MarkRounding message;
int intMsgVer = ByteConverter.bytesToInt(messageVersionNumber);
long lngTime = ByteConverter.bytesToLong(byteTime);
int intAck = ByteConverter.bytesToInt(byteAck);
int intRaceID = ByteConverter.bytesToInt(byteRaceID);
int intSourceID = ByteConverter.bytesToInt(byteSourceID);
int intBoatState = ByteConverter.bytesToInt(byteBoatStatus);
int intRoundingSide = ByteConverter.bytesToInt(byteRoundingSide);
int intMarkType = ByteConverter.bytesToInt(byteMarkType);
int intMarkID = ByteConverter.bytesToInt(byteMarkID);
markRounding = new MarkRounding(intMsgVer, lngTime, intAck, intRaceID, intSourceID, intBoatState, intRoundingSide, intMarkType, intMarkID);
/**
* Constructs a decoder to decode a given message.
*/
public MarkRoundingDecoder() {
}
public MarkRounding getMarkRounding() {
return markRounding;
@Override
public AC35Data decode(byte[] encodedMessage) throws InvalidMessageException {
this.encodedMessage = encodedMessage;
try {
byte messageVersionNumber = encodedMessage[0];
byte[] byteTime = Arrays.copyOfRange(encodedMessage, 1, 7);
long time = ByteConverter.bytesToLong(byteTime);
byte[] byteAck = Arrays.copyOfRange(encodedMessage, 7, 9);
int ackNumber = ByteConverter.bytesToInt(byteAck);
byte[] byteRaceID = Arrays.copyOfRange(encodedMessage, 9, 13);
int raceID = ByteConverter.bytesToInt(byteRaceID);
byte[] byteSourceID = Arrays.copyOfRange(encodedMessage, 13, 17);
int sourceID = ByteConverter.bytesToInt(byteSourceID);
byte byteBoatStatus = encodedMessage[17];
MarkRoundingBoatStatusEnum boatStatus = MarkRoundingBoatStatusEnum.fromByte(byteBoatStatus);
byte byteRoundingSide = encodedMessage[18];
MarkRoundingSideEnum roundingSide = MarkRoundingSideEnum.fromByte(byteRoundingSide);
byte byteMarkType = encodedMessage[19];
MarkRoundingTypeEnum markType = MarkRoundingTypeEnum.fromByte(byteMarkType);
byte byteMarkID = encodedMessage[20];
message = new MarkRounding(
messageVersionNumber,
time,
ackNumber,
raceID,
sourceID,
boatStatus,
roundingSide,
markType,
byteMarkID);
return message;
} catch (Exception e) {
throw new InvalidMessageException("Could not decode AverageWind message.", e);
}
}
/**
* Returns the decoded message.
*
* @return The decoded message.
*/
public MarkRounding getMessage() {
return message;
}
}
@@ -0,0 +1,23 @@
package network.MessageDecoders;
import network.Exceptions.InvalidMessageException;
import network.Messages.AC35Data;
/**
* This is the interface that all message decoders must implement.
* It allows for {@link #decode(byte[])}ing messages.
*/
public interface MessageDecoder {
/**
* Decodes a given message.
* @param encodedMessage The message to decode.
* @return The decoded message.
* @throws InvalidMessageException Thrown if the encoded message is invalid in some way, or cannot be decoded.
*/
public AC35Data decode(byte[] encodedMessage) throws InvalidMessageException;
}
@@ -1,66 +1,86 @@
package network.MessageDecoders;
import network.Exceptions.InvalidMessageException;
import network.Messages.AC35Data;
import network.Messages.Enums.RaceStartTypeEnum;
import network.Messages.RaceStartStatus;
import java.util.Arrays;
import static network.Utils.ByteConverter.*;
/**
* Created by hba56 on 21/04/17.
* Decodes {@link RaceStartStatus} messages.
*/
public class RaceStartStatusDecoder {
private byte messageVersion;
private byte[] timestamp;
private byte[] ackNumber;
private byte[] raceStartTime;
private byte[] raceIdentifier;
private byte notificationType;
public class RaceStartStatusDecoder implements MessageDecoder {
private long time;
private short ack;
private long startTime;
private int raceID;
private char notification;
/**
* The encoded message.
*/
private byte[] encodedMessage;
/**
* The decoded message.
*/
private RaceStartStatus message;
public RaceStartStatusDecoder(byte[] encodedRaceStartStatus) {
messageVersion = encodedRaceStartStatus[0];
timestamp = Arrays.copyOfRange(encodedRaceStartStatus, 1, 7);
ackNumber = Arrays.copyOfRange(encodedRaceStartStatus, 7, 9);
raceStartTime = Arrays.copyOfRange(encodedRaceStartStatus, 9, 15);
raceIdentifier = Arrays.copyOfRange(encodedRaceStartStatus, 15, 19);
notificationType = encodedRaceStartStatus[19];
time = bytesToLong(timestamp);
ack = bytesToShort(ackNumber);
startTime = bytesToLong(raceStartTime);
raceID = bytesToInt(raceIdentifier);
notification = bytesToChar(notificationType);
/**
* Constructs a decoder to decode a given message.
*/
public RaceStartStatusDecoder() {
}
public byte getMessageVersion() {
return messageVersion;
@Override
public AC35Data decode(byte[] encodedMessage) throws InvalidMessageException {
this.encodedMessage = encodedMessage;
try {
byte messageVersion = encodedMessage[0];
byte[] timestamp = Arrays.copyOfRange(encodedMessage, 1, 7);
long time = bytesToLong(timestamp);
byte[] ackNumber = Arrays.copyOfRange(encodedMessage, 7, 9);
short ack = bytesToShort(ackNumber);
byte[] raceStartTime = Arrays.copyOfRange(encodedMessage, 9, 15);
long startTime = bytesToLong(raceStartTime);
byte[] raceIdentifier = Arrays.copyOfRange(encodedMessage, 15, 19);
int raceID = bytesToInt(raceIdentifier);
byte notificationType = encodedMessage[19];
message = new RaceStartStatus(
messageVersion,
time,
ack,
startTime,
raceID,
RaceStartTypeEnum.fromByte(notificationType)
);
return message;
} catch (Exception e) {
throw new InvalidMessageException("Could not decode RaceStartStatus message.", e);
}
}
public long getTime() {
return time;
}
public short getAck() {
return ack;
}
public long getStartTime() {
return startTime;
}
public int getRaceID() {
return raceID;
}
public char getNotification() {
return notification;
/**
* Returns the decoded message.
* @return The decoded message.
*/
public RaceStartStatus getMessage() {
return message;
}
}
@@ -1,119 +1,125 @@
package network.MessageDecoders;
import network.Exceptions.InvalidMessageException;
import network.Messages.AC35Data;
import network.Messages.BoatStatus;
import network.Messages.Enums.RaceStatusEnum;
import network.Messages.Enums.RaceTypeEnum;
import network.Messages.RaceStatus;
import network.Utils.AC35UnitConverter;
import shared.model.Bearing;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import static network.Utils.ByteConverter.*;
/**
* 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;
public class RaceStatusDecoder implements MessageDecoder {
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<BoatStatus> boats = new ArrayList<>();
/**
* The encoded message.
*/
private byte[] encodedMessage;
/**
* The decoded message.
*/
private RaceStatus message;
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);
/**
* Constructs a decoder to decode a given message.
*/
public RaceStatusDecoder() {
}
int boatLoopIndex = 0;
for (int i=0; i < numberOfBoats; i++) {
byte[] boatBytes = Arrays.copyOfRange(boatsBytes, boatLoopIndex, boatLoopIndex+20);
@Override
public AC35Data decode(byte[] encodedMessage) throws InvalidMessageException {
this.encodedMessage = encodedMessage;
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);
try {
BoatStatus boat = new BoatStatus(bytesToInt(sourceID),boatStatus,
legNumber, numPenaltiesAwarded, numPenaltiesServed,
bytesToLong(estTimeAtNextMark), bytesToLong(estTimeAtFinish));
byte[] versionNumBytes = Arrays.copyOfRange(encodedMessage, 0, 1);
byte versionNum = versionNumBytes[0];
boats.add(boat);
boatLoopIndex += 20;
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);
byte[] windDirectionBytes = Arrays.copyOfRange(encodedMessage, 18, 20);
int windDirectionInt = bytesToInt(windDirectionBytes);
Bearing windDirection = Bearing.fromDegrees(AC35UnitConverter.unpackHeading(windDirectionInt));
byte[] windSpeedBytes = Arrays.copyOfRange(encodedMessage, 20, 22);
int windSpeedInt = bytesToInt(windSpeedBytes);
double windSpeedKnots = AC35UnitConverter.unpackMMperSecToKnots(windSpeedInt);
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<BoatStatus> boatStatuses = new ArrayList<>();
//BoatStatus is 20 bytes.
int boatStatusByteLength = 20;
//Decode each BoatStatus.
for (int boatLoopIndex = 0; boatLoopIndex < (numberOfBoats * boatStatusByteLength); boatLoopIndex += boatStatusByteLength) {
byte[] boatStatusBytes = Arrays.copyOfRange(boatStatusesBytes, boatLoopIndex, boatLoopIndex + boatStatusByteLength);
BoatStatusDecoder boatStatusDecoder = new BoatStatusDecoder();
boatStatuses.add(boatStatusDecoder.decode(boatStatusBytes));
}
message = new RaceStatus(
versionNum,
time,
raceID,
raceStatus,
expectedStart,
windDirection,
windSpeedKnots,
raceType,
boatStatuses);
return message;
} catch (Exception e) {
throw new InvalidMessageException("Could not decode RaceStatus message.", e);
}
}
public byte getVersionNum() {
return versionNum;
}
public long getTime() {
return time;
}
public int getRace() {
return race;
}
public byte getRaceState() {
return raceState;
}
public long getStartTime() {
return startTime;
}
public int getRaceWindDir() {
return raceWindDir;
}
public short getRaceWindSpeed() {
return raceWindSpeed;
}
public int getNumberOfBoats() {
return numberOfBoats;
}
public int getRaceType() {
return raceType;
}
public ArrayList<BoatStatus> getBoats() {
return boats;
/**
* Returns the decoded message.
* @return The decoded message.
*/
public RaceStatus getMessage() {
return message;
}
}
@@ -0,0 +1,67 @@
package network.MessageDecoders;
import network.Exceptions.InvalidMessageException;
import network.Messages.AC35Data;
import network.Messages.Enums.RequestToJoinEnum;
import network.Messages.RequestToJoin;
import network.Utils.ByteConverter;
import java.util.Arrays;
/**
* Decoder for {@link network.Messages.RequestToJoin} messages.
*/
public class RequestToJoinDecoder implements MessageDecoder{
/**
* The encoded message.
*/
private byte[] encodedRequest;
/**
* The decoded message.
*/
private RequestToJoin message;
/**
* Constructs a decoder to decode a given message.
*/
public RequestToJoinDecoder() {
}
@Override
public AC35Data decode(byte[] encodedRequest) throws InvalidMessageException {
this.encodedRequest = encodedRequest;
try {
//Request type is first four bytes.
byte[] requestTypeBytes = Arrays.copyOfRange(encodedRequest, 0, 4);
//Request type is an integral type.
int requestTypeInt = ByteConverter.bytesToInt(requestTypeBytes);
RequestToJoinEnum requestType = RequestToJoinEnum.fromInt(requestTypeInt);
message = new RequestToJoin(requestType);
return message;
} catch (Exception e) {
throw new InvalidMessageException("Could not decode RequestToJoin message.", e);
}
}
/**
* Returns the decoded message.
* @return The decoded message.
*/
public RequestToJoin getMessage() {
return message;
}
}
@@ -1,6 +1,9 @@
package network.MessageDecoders;
import network.Exceptions.InvalidMessageException;
import network.Messages.AC35Data;
import network.Messages.Enums.XMLMessageType;
import network.Messages.XMLMessage;
import java.util.Arrays;
@@ -9,73 +12,74 @@ import static network.Utils.ByteConverter.bytesToShort;
/**
* Created by hba56 on 20/04/17.
* Decodes {@link network.Messages.XMLMessage} messages.
*/
public class XMLMessageDecoder {
private byte messageVersionNumber;
private short ackNumber;
private long timeStamp;
private byte xmlMsgSubType;
private short sequenceNumber;
private short xmlMsgLength;
private String xmlMessage;
public class XMLMessageDecoder implements MessageDecoder {
private byte[] bytes;
/**
* The encoded message.
*/
private byte[] encodedMessage;
public XMLMessageDecoder(byte[] bytes) {
this.bytes = bytes;
}
public void decode(){
byte[] ackNumberBytes = Arrays.copyOfRange(bytes, 1, 3);
byte[] timeStampBytes = Arrays.copyOfRange(bytes, 3, 9);
byte[] sequenceNumberBytes = Arrays.copyOfRange(bytes, 10, 12);
byte[] xmlMsgLengthBytes = Arrays.copyOfRange(bytes, 12, 14);
byte[] xmlMessagebytes = Arrays.copyOfRange(bytes, 14, bytes.length);
this.xmlMsgSubType = bytes[9];
this.messageVersionNumber = bytes[0];
this.ackNumber = bytesToShort(ackNumberBytes);
this.timeStamp = bytesToLong(timeStampBytes);
this.sequenceNumber = bytesToShort(sequenceNumberBytes);
this.xmlMsgLength = bytesToShort(xmlMsgLengthBytes);
this.xmlMessage = new String(xmlMessagebytes).trim();
}
public byte getMessageVersionNumber() {
return messageVersionNumber;
}
public short getAckNumber() {
return ackNumber;
}
public long getTimeStamp() {
return timeStamp;
}
public XMLMessageType getXmlMsgSubType() {
return XMLMessageType.fromByte(xmlMsgSubType);
}
public short getSequenceNumber() {
return sequenceNumber;
}
public short getXmlMsgLength() {
return xmlMsgLength;
}
/**
* The decoded message.
*/
private XMLMessage message;
/**
* Returns the contents of the XML message (e.g., the contents of a race.xml file).
* @return The contents of the XML message.
* Constructs a decoder to decode a given message.
*/
public String getXmlMessageContents() {
return xmlMessage;
public XMLMessageDecoder() {
}
@Override
public AC35Data decode(byte[] encodedMessage) throws InvalidMessageException {
this.encodedMessage = encodedMessage;
try {
byte[] messageVersionNumberBytes = Arrays.copyOfRange(encodedMessage, 0, 1);
byte[] ackNumberBytes = Arrays.copyOfRange(encodedMessage, 1, 3);
byte[] timeStampBytes = Arrays.copyOfRange(encodedMessage, 3, 9);
byte[] xmlMsgSubTypeBytes = Arrays.copyOfRange(encodedMessage, 9, 10);
byte[] sequenceNumberBytes = Arrays.copyOfRange(encodedMessage, 10, 12);
byte[] xmlMsgLengthBytes = Arrays.copyOfRange(encodedMessage, 12, 14);
byte[] xmlMessagebytes = Arrays.copyOfRange(encodedMessage, 14, encodedMessage.length);
byte messageVersionNumber = messageVersionNumberBytes[0];
short ackNumber = bytesToShort(ackNumberBytes);
long timeStamp = bytesToLong(timeStampBytes);
XMLMessageType xmlMsgSubType = XMLMessageType.fromByte(xmlMsgSubTypeBytes[0]);
short sequenceNumber = bytesToShort(sequenceNumberBytes);
short xmlMsgLength = bytesToShort(xmlMsgLengthBytes);
String xmlMessage = new String(xmlMessagebytes);
message = new XMLMessage(
messageVersionNumber,
ackNumber,
timeStamp,
xmlMsgSubType,
sequenceNumber,
xmlMessage);
return message;
} catch (Exception e) {
throw new InvalidMessageException("Could not decode XMLMessage message.", e);
}
}
/**
* Returns the decoded message.
* @return The decoded message.
*/
public XMLMessage getMessage() {
return message;
}
}
@@ -0,0 +1,92 @@
package network.MessageEncoders;
import network.Exceptions.InvalidMessageException;
import network.Messages.AC35Data;
import network.Messages.AverageWind;
import java.nio.ByteBuffer;
import static network.Utils.AC35UnitConverter.*;
import static network.Utils.ByteConverter.intToBytes;
import static network.Utils.ByteConverter.longToBytes;
/**
* This encoder can encode a {@link AverageWind} message.
*/
public class AverageWindEncoder implements MessageEncoder {
/**
* Constructor.
*/
public AverageWindEncoder() {
}
@Override
public byte[] encode(AC35Data message) throws InvalidMessageException {
try {
//Downcast.
AverageWind averageWind = (AverageWind) message;
byte messageVersionNumber = averageWind.getMessageVersionNumber();
long time = averageWind.getTime();
byte[] byteTime = longToBytes(time, 6);
long rawPeriod = averageWind.getRawPeriod();
int rawPeriodInt = packAverageWindPeriod(rawPeriod);
byte[] byteRawPeriod = intToBytes(rawPeriodInt, 2);
double rawSampleSpeed = averageWind.getRawSpeedKnots();
int rawSampleSpeedInt = packKnotsToMMperSec(rawSampleSpeed);
byte[] byteRawSpeed = intToBytes(rawSampleSpeedInt, 2);
long period2 = averageWind.getSampleTwoPeriod();
int period2Int = packAverageWindPeriod(period2);
byte[] bytePeriod2 = intToBytes(period2Int, 2);
double speed2 = averageWind.getSampleTwoSpeedKnots();
int speed2Int = packKnotsToMMperSec(speed2);
byte[] byteSpeed2 = intToBytes(speed2Int, 2);
long period3 = averageWind.getSampleThreePeriod();
int period3Int = packAverageWindPeriod(period3);
byte[] bytePeriod3 = intToBytes(period3Int, 2);
double speed3 = averageWind.getSampleThreeSpeedKnots();
int speed3Int = packKnotsToMMperSec(speed3);
byte[] byteSpeed3 = intToBytes(speed3Int, 2);
long period4 = averageWind.getSampleFourPeriod();
int period4Int = packAverageWindPeriod(period4);
byte[] bytePeriod4 = intToBytes(period4Int, 2);
double speed4 = averageWind.getSampleFourSpeedKnots();
int speed4Int = packKnotsToMMperSec(speed4);
byte[] byteSpeed4 = intToBytes(speed4Int, 2);
ByteBuffer result = ByteBuffer.allocate(23);
result.put(messageVersionNumber);
result.put(byteTime);
result.put(byteRawPeriod);
result.put(byteRawSpeed);
result.put(bytePeriod2);
result.put(byteSpeed2);
result.put(bytePeriod3);
result.put(byteSpeed3);
result.put(bytePeriod4);
result.put(byteSpeed4);
return result.array();
} catch (Exception e) {
throw new InvalidMessageException("Could not encode AverageWind message.", e);
}
}
}
@@ -0,0 +1,47 @@
package network.MessageEncoders;
import network.Exceptions.InvalidMessageException;
import network.Messages.AC35Data;
import network.Messages.BoatAction;
import java.nio.ByteBuffer;
import static network.Utils.ByteConverter.intToBytes;
/**
* This encoder can encode a {@link BoatAction} message.
*/
public class BoatActionEncoder implements MessageEncoder {
/**
* Constructor.
*/
public BoatActionEncoder() {
}
@Override
public byte[] encode(AC35Data message) throws InvalidMessageException {
try {
//Downcast.
BoatAction boatAction = (BoatAction) message;
//Message is 1 byte.
ByteBuffer boatActionMessage = ByteBuffer.allocate(1);
boatActionMessage.put(intToBytes(boatAction.getBoatAction().getValue(), 1));
byte[] result = boatActionMessage.array();
return result;
} catch (Exception e) {
throw new InvalidMessageException("Could not encode BoatAction message.", e);
}
}
}
@@ -0,0 +1,91 @@
package network.MessageEncoders;
import network.Exceptions.InvalidMessageException;
import network.Messages.AC35Data;
import network.Messages.BoatLocation;
import java.nio.ByteBuffer;
import static network.Utils.AC35UnitConverter.*;
import static network.Utils.ByteConverter.intToBytes;
import static network.Utils.ByteConverter.longToBytes;
/**
* This encoder can encode a {@link BoatLocation} message.
*/
public class BoatLocationEncoder implements MessageEncoder {
/**
* Constructor.
*/
public BoatLocationEncoder() {
}
@Override
public byte[] encode(AC35Data message) throws InvalidMessageException {
try {
//Downcast.
BoatLocation boatLocation = (BoatLocation) message;
int messageVersionNumber = 0b1;
byte[] messageVersionBytes = intToBytes(messageVersionNumber, 1);
byte[] time = longToBytes(boatLocation.getTime(), 6);
byte[] sourceID = intToBytes(boatLocation.getSourceID(), 4);
byte[] seqNum = longToBytes(boatLocation.getSequenceNumber(), 4);
byte[] deviceType = intToBytes(boatLocation.getDeviceType().getValue(), 1);
byte[] latitude = intToBytes(packGPS(boatLocation.getLatitude()), 4);
byte[] longitude = intToBytes(packGPS(boatLocation.getLongitude()), 4);
byte[] altitude = intToBytes(boatLocation.getAltitude(), 4);
byte[] heading = intToBytes(packHeading(boatLocation.getHeading().degrees()), 2);
byte[] pitch = intToBytes(boatLocation.getPitch(), 2);
byte[] roll = intToBytes(boatLocation.getRoll(), 2);
byte[] boatSpeed = intToBytes(packKnotsToMMperSec(boatLocation.getBoatSpeedKnots()), 2);
byte[] cog = intToBytes(packHeading(boatLocation.getBoatCOG().degrees()), 2);
byte[] sog = intToBytes(packKnotsToMMperSec(boatLocation.getBoatSOGKnots()), 2);
byte[] apparentWindSpeed = intToBytes(packKnotsToMMperSec(boatLocation.getApparentWindSpeedKnots()), 2);
byte[] apparentWindAngle = intToBytes(packTrueWindAngle(boatLocation.getApparentWindAngle().degrees()), 2);
byte[] trueWindSpeed = intToBytes(packKnotsToMMperSec(boatLocation.getTrueWindSpeedKnots()), 2);
byte[] trueWindDirection = intToBytes(packHeading(boatLocation.getTrueWindDirection().degrees()), 2);
byte[] trueWindAngle = intToBytes(packTrueWindAngle(boatLocation.getTrueWindAngle().degrees()), 2);
byte[] currentDrift = intToBytes(packKnotsToMMperSec(boatLocation.getCurrentDriftKnots()), 2);
byte[] currentSet = intToBytes(packHeading(boatLocation.getCurrentSet().degrees()), 2);
byte[] rudderAngle = intToBytes(packTrueWindAngle(boatLocation.getRudderAngle().degrees()), 2);
ByteBuffer result = ByteBuffer.allocate(56);
result.put(messageVersionBytes);
result.put(time);
result.put(sourceID);
result.put(seqNum);
result.put(deviceType);
result.put(latitude);
result.put(longitude);
result.put(altitude);
result.put(heading);
result.put(pitch);
result.put(roll);
result.put(boatSpeed);
result.put(cog);
result.put(sog);
result.put(apparentWindSpeed);
result.put(apparentWindAngle);
result.put(trueWindSpeed);
result.put(trueWindDirection);
result.put(trueWindAngle);
result.put(currentDrift);
result.put(currentSet);
result.put(rudderAngle);
return result.array();
} catch (Exception e) {
throw new InvalidMessageException("Could not encode BoatLocation message.", e);
}
}
}
@@ -0,0 +1,65 @@
package network.MessageEncoders;
import network.Exceptions.InvalidMessageException;
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.
* @throws InvalidMessageException Thrown if the message is invalid in some way, or cannot be encoded.
*/
public byte[] encode(BoatStatus message) throws InvalidMessageException {
try {
//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();
} catch (Exception e) {
throw new InvalidMessageException("Could not encode BoatStatus message.", e);
}
}
}
@@ -0,0 +1,81 @@
package network.MessageEncoders;
import network.Exceptions.InvalidMessageException;
import network.Messages.CourseWind;
import shared.model.Bearing;
import java.nio.ByteBuffer;
import java.util.Arrays;
import static network.Utils.AC35UnitConverter.*;
import static network.Utils.ByteConverter.*;
import static network.Utils.ByteConverter.bytesToInt;
/**
* This encoder can encode a {@link CourseWind} message.
*/
public class CourseWindEncoder {
/**
* Constructor.
*/
public CourseWindEncoder() {
}
/**
* Encodes a given CourseWind message.
* @param message The message to encode.
* @return The encoded message.
* @throws InvalidMessageException Thrown if the message is invalid in some way, or cannot be encoded.
*/
public byte[] encode(CourseWind message) throws InvalidMessageException {
try {
CourseWind courseWind = message;
//CourseWind is 20 bytes.
ByteBuffer courseWindBuffer = ByteBuffer.allocate(20);
byte[] windId = intToBytes(courseWind.getID(), 1);
byte[] timeBytes = longToBytes(courseWind.getTime(), 6);
byte[] raceIDBytes = intToBytes(courseWind.getRaceID(), 4);
int windDirectionInt = packHeading(courseWind.getWindDirection().degrees());
byte[] windDirectionBytes = intToBytes(windDirectionInt, 2);
int windSpeedInt = packKnotsToMMperSec(courseWind.getWindSpeedKnots());
byte[] windSpeedBytes = intToBytes(windSpeedInt, 2);
int bestUpwindAngleInt = packHeading(courseWind.getBestUpwindAngle().degrees());
byte[] bestUpwindAngleBytes = intToBytes(bestUpwindAngleInt, 2);
int bestDownwindAngleInt = packHeading(courseWind.getBestDownwindAngle().degrees());
byte[] bestDownwindAngleBytes = intToBytes(bestDownwindAngleInt, 2);
byte[] flags = intToBytes(courseWind.getFlags(), 1);
courseWindBuffer.put(windId);
courseWindBuffer.put(timeBytes);
courseWindBuffer.put(raceIDBytes);
courseWindBuffer.put(windDirectionBytes);
courseWindBuffer.put(windSpeedBytes);
courseWindBuffer.put(bestUpwindAngleBytes);
courseWindBuffer.put(bestDownwindAngleBytes);
courseWindBuffer.put(flags);
return courseWindBuffer.array();
} catch (Exception e) {
throw new InvalidMessageException("Could not encode CourseWind message.", e);
}
}
}
@@ -0,0 +1,64 @@
package network.MessageEncoders;
import network.Exceptions.InvalidMessageException;
import network.Messages.AC35Data;
import network.Messages.BoatAction;
import network.Messages.CourseWind;
import network.Messages.CourseWinds;
import java.nio.ByteBuffer;
import static network.Utils.ByteConverter.intToBytes;
/**
* This encoder can encode a {@link CourseWinds} message.
*/
public class CourseWindsEncoder implements MessageEncoder {
/**
* Constructor.
*/
public CourseWindsEncoder() {
}
@Override
public byte[] encode(AC35Data message) throws InvalidMessageException {
try {
//Downcast.
CourseWinds courseWinds = (CourseWinds) message;
byte messageVersionNumber = CourseWinds.currentMessageVersionNumber;
byte byteWindID = courseWinds.getSelectedWindID();
byte[] loopcount = intToBytes(courseWinds.getCourseWinds().size(), 1);
ByteBuffer result = ByteBuffer.allocate(3 + 20 * courseWinds.getCourseWinds().size());
result.put(messageVersionNumber);
result.put(byteWindID);
result.put(loopcount);
//Encode each CourseWind.
for (CourseWind wind : courseWinds.getCourseWinds()) {
CourseWindEncoder courseWindEncoder = new CourseWindEncoder();
byte[] encodedCourseWind = courseWindEncoder.encode(wind);
result.put(encodedCourseWind);
}
return result.array();
} catch (Exception e) {
throw new InvalidMessageException("Could not encode CourseWinds message.", e);
}
}
}
@@ -0,0 +1,70 @@
package network.MessageEncoders;
import network.Exceptions.InvalidMessageTypeException;
import network.Messages.Enums.MessageType;
/**
* Factory to create the appropriate encoder for a given message.
*/
public class EncoderFactory {
/**
* Private constructor. Currently doesn't need to be constructed.
*/
private EncoderFactory(){
}
/**
* Creates the correct type of encoder for a given message type.
* @param type Type of message you want an encoder for.
* @return The encoder.
* @throws InvalidMessageTypeException If you pass in a {@link MessageType} that isn't recognised.
*/
public static MessageEncoder create(MessageType type) throws InvalidMessageTypeException {
switch (type) {
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();
//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();
case COURSEWIND: return new CourseWindsEncoder();
case AVGWIND: return new AverageWindEncoder();
case REQUEST_TO_JOIN: return new RequestToJoinEncoder();
case JOIN_ACCEPTANCE: return new JoinAcceptanceEncoder();
case BOATACTION: return new BoatActionEncoder();
default: throw new InvalidMessageTypeException("Unrecognised message type: " + type);
}
}
}
@@ -0,0 +1,46 @@
package network.MessageEncoders;
import network.Exceptions.InvalidMessageException;
import network.Messages.AC35Data;
import network.Messages.HeartBeat;
import java.nio.ByteBuffer;
import static network.Utils.ByteConverter.longToBytes;
/**
* This encoder can encode a {@link HeartBeat} message.
*/
public class HeartBeatEncoder implements MessageEncoder {
/**
* Constructor.
*/
public HeartBeatEncoder() {
}
@Override
public byte[] encode(AC35Data message) throws InvalidMessageException {
try {
//Downcast.
HeartBeat heartbeat = (HeartBeat) message;
//Message is 4 bytes.
ByteBuffer heartBeat = ByteBuffer.allocate(4);
heartBeat.put(longToBytes(heartbeat.getSequenceNumber(), 4));
byte[] result = heartBeat.array();
return result;
} catch (Exception e) {
throw new InvalidMessageException("Could not encode HeartBeat message.", e);
}
}
}
@@ -0,0 +1,51 @@
package network.MessageEncoders;
import network.Exceptions.InvalidMessageException;
import network.Messages.AC35Data;
import network.Messages.JoinAcceptance;
import network.Utils.ByteConverter;
import java.nio.ByteBuffer;
import static network.Utils.ByteConverter.intToBytes;
/**
* This encoder can encode a {@link JoinAcceptance} message.
*/
public class JoinAcceptanceEncoder implements MessageEncoder {
/**
* Constructor.
*/
public JoinAcceptanceEncoder() {
}
@Override
public byte[] encode(AC35Data message) throws InvalidMessageException {
try {
//Downcast.
JoinAcceptance joinAcceptance = (JoinAcceptance) message;
//Message is 5 bytes.
ByteBuffer joinAcceptanceBuffer = ByteBuffer.allocate(5);
//Source ID is first four bytes.
joinAcceptanceBuffer.put(intToBytes(joinAcceptance.getSourceID(), 4));
//Acceptance type is next byte.
joinAcceptanceBuffer.put(intToBytes(joinAcceptance.getAcceptanceType().getValue(), 1));
byte[] result = joinAcceptanceBuffer.array();
return result;
} catch (Exception e) {
throw new InvalidMessageException("Could not encode JoinAcceptance message.", e);
}
}
}
@@ -0,0 +1,64 @@
package network.MessageEncoders;
import network.Exceptions.InvalidMessageException;
import network.Messages.AC35Data;
import network.Messages.MarkRounding;
import java.nio.ByteBuffer;
import static network.Utils.ByteConverter.intToBytes;
import static network.Utils.ByteConverter.longToBytes;
/**
* This encoder can encode a {@link MarkRounding} message.
*/
public class MarkRoundingEncoder implements MessageEncoder {
/**
* Constructor.
*/
public MarkRoundingEncoder() {
}
@Override
public byte[] encode(AC35Data message) throws InvalidMessageException {
try {
//Downcast.
MarkRounding markRounding = (MarkRounding) message;
byte messageVersionNumber = markRounding.getMessageVersionNumber();
byte[] byteTime = longToBytes(markRounding.getTime(), 6);
byte[] byteAck = intToBytes(markRounding.getAckNum(), 2);
byte[] byteRaceID = intToBytes(markRounding.getRaceID(), 4);
byte[] byteSourceID = intToBytes(markRounding.getSourceID(), 4);
byte[] byteBoatStatus = intToBytes(markRounding.getBoatStatus().getValue(), 1);
byte[] byteRoundingSide = intToBytes(markRounding.getRoundingSide().getValue(), 1);
byte[] byteMarkType = intToBytes(markRounding.getMarkType().getValue(), 1);
byte[] byteMarkID = intToBytes(markRounding.getMarkID(), 1);
ByteBuffer result = ByteBuffer.allocate(21);
result.put(messageVersionNumber);
result.put(byteTime);
result.put(byteAck);
result.put(byteRaceID);
result.put(byteSourceID);
result.put(byteBoatStatus);
result.put(byteRoundingSide);
result.put(byteMarkType);
result.put(byteMarkID);
return result.array();
} catch (Exception e) {
throw new InvalidMessageException("Could not encode MarkRounding message.", e);
}
}
}
@@ -0,0 +1,23 @@
package network.MessageEncoders;
import network.Exceptions.InvalidMessageException;
import network.Messages.AC35Data;
/**
* This is the interface that all message encoders must implement.
* It allows for {@link #encode(AC35Data)}ing messages.
*/
public interface MessageEncoder {
/**
* Encodes a given message.
* @param message The message to encode.
* @return Message in byte encoded form.
* @throws InvalidMessageException Thrown if the message is invalid in some way, or cannot be encoded.
*/
public byte[] encode(AC35Data message) throws InvalidMessageException;
}
@@ -0,0 +1,58 @@
package network.MessageEncoders;
import network.Exceptions.InvalidMessageException;
import network.Messages.AC35Data;
import network.Messages.RaceStartStatus;
import network.Utils.ByteConverter;
import java.nio.ByteBuffer;
import static network.Utils.ByteConverter.intToBytes;
import static network.Utils.ByteConverter.longToBytes;
/**
* This encoder can encode a {@link RaceStartStatus} message.
*/
public class RaceStartStatusEncoder implements MessageEncoder {
/**
* Constructor.
*/
public RaceStartStatusEncoder() {
}
@Override
public byte[] encode(AC35Data message) throws InvalidMessageException {
try {
//Downcast.
RaceStartStatus raceStartStatus = (RaceStartStatus) message;
byte messageVersion = raceStartStatus.getMessageVersionNumber();
byte[] timestamp = longToBytes(raceStartStatus.getTimestamp(), 6);
byte[] ackNumber = intToBytes(raceStartStatus.getAckNum(), 2);
byte[] raceStartTime = longToBytes(raceStartStatus.getRaceStartTime(), 6);
byte[] raceIdentifier = intToBytes(raceStartStatus.getRaceID());
byte[] notificationType = intToBytes(raceStartStatus.getNotificationType().getValue(), 1);
ByteBuffer result = ByteBuffer.allocate(20);
result.put(messageVersion);
result.put(timestamp);
result.put(ackNumber);
result.put(raceStartTime);
result.put(raceIdentifier);
result.put(notificationType);
return result.array();
} catch (Exception e) {
throw new InvalidMessageException("Could not encode RaceStartStatus message.", e);
}
}
}
@@ -0,0 +1,102 @@
package network.MessageEncoders;
import network.Exceptions.InvalidMessageException;
import network.Messages.AC35Data;
import network.Messages.BoatStatus;
import network.Messages.RaceStatus;
import network.Utils.AC35UnitConverter;
import shared.model.Bearing;
import java.nio.ByteBuffer;
import java.util.List;
import static network.Utils.ByteConverter.bytesToInt;
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) throws InvalidMessageException {
try {
//Downcast.
RaceStatus raceStatus = (RaceStatus) message;
List<BoatStatus> 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.
int windDirectionInt = AC35UnitConverter.packHeading(raceStatus.getWindDirection().degrees());
byte[] raceWind = intToBytes(windDirectionInt, 2);
//mm/sec
int windSpeedInt = AC35UnitConverter.packKnotsToMMperSec(raceStatus.getWindSpeed());
byte[] windSpeed = intToBytes(windSpeedInt, 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();
} catch (Exception e) {
throw new InvalidMessageException("Could not encode RaceStatus message.", e);
}
}
}
@@ -1,7 +1,11 @@
package network.MessageEncoders;
import network.BinaryMessageEncoder;
import network.Exceptions.InvalidMessageException;
import network.Exceptions.InvalidMessageTypeException;
import network.Messages.*;
import network.Messages.Enums.MessageType;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
@@ -18,72 +22,8 @@ import static network.Utils.ByteConverter.longToBytes;
*/
public class RaceVisionByteEncoder {
/**
* Serializes a heartbeat message.
* @param heartbeat Heartbeat message.
* @return Serialized message.
*/
public static byte[] heartBeat(Heartbeat heartbeat) {
ByteBuffer heartBeat = ByteBuffer.allocate(4);
heartBeat.put(longToBytes(heartbeat.getSequenceNumber(), 4));
byte[] result = heartBeat.array();
return result;
}
/**
* 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<BoatStatus> 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);
@@ -126,25 +66,6 @@ public class RaceVisionByteEncoder {
return result.array();
}
public static byte[] raceStartStatus(long time, short ack, long startTime, int raceID, char notification){
int messageVersion = 0b1;
byte[] timestamp = longToBytes(time, 6);
byte[] ackNumber = intToBytes(ack, 2);
byte[] raceStartTime = longToBytes(startTime, 6);
int raceIdentifier = raceID;
byte[] notificationType = intToBytes(notification, 1);
ByteBuffer result = ByteBuffer.allocate(20);
result.put(intToBytes(messageVersion, 1));
result.put(timestamp);
result.put(ackNumber);
result.put(raceStartTime);
result.put(intToBytes(raceIdentifier));
result.put(notificationType);
return result.array();
}
public static byte[] yachtEventCode(long time, short acknowledgeNumber, int raceID, int destSourceID, int incidentID,
int eventID){
int messageVersion = 0b10;
@@ -182,169 +103,54 @@ public class RaceVisionByteEncoder {
}
/**
* Serializes an xml message into a byte array.
* @param xmlMessage The message to serialize.
* @return byte array contaning serialized message.
* Encodes a given message, to be placed inside a binary message (see {@link BinaryMessageEncoder}).
* @param message Message to encode.
* @return Encoded message.
* @throws InvalidMessageException If the message cannot be encoded.
*/
public static byte[] xmlMessage(XMLMessage xmlMessage) {
public static byte[] encode(AC35Data message) throws InvalidMessageException {
MessageEncoder encoder = null;
try {
encoder = EncoderFactory.create(message.getType());
byte[] messageBytes = xmlMessage.getXmlMessage().getBytes(StandardCharsets.UTF_8);
} catch (InvalidMessageTypeException e) {
throw new InvalidMessageException("Could not create encoder for MessageType: " + message.getType(), e);
ByteBuffer tempOutputByteBuffer = ByteBuffer.allocate(14 + messageBytes.length);
//ackNumber converted to bytes
byte[] ackNumberBytes = intToBytes(xmlMessage.getAckNumber(), 2);
//Timestamp converted to bytes.
byte[] timestampBytes = longToBytes(xmlMessage.getTimeStamp(), 6);
//sequenceNumber converted to bytes
byte[] sequenceNumberBytes = intToBytes(xmlMessage.getSequenceNumber(), 2);
//xmlMsgLength converted to bytes
byte[] xmlMsgLengthBytes = intToBytes(xmlMessage.getXmlMsgLength(), 2);
tempOutputByteBuffer.put(xmlMessage.getVersionNumber());
tempOutputByteBuffer.put(ackNumberBytes);
tempOutputByteBuffer.put(timestampBytes);
tempOutputByteBuffer.put(xmlMessage.getXmlMsgSubType().getValue());
tempOutputByteBuffer.put(sequenceNumberBytes);
tempOutputByteBuffer.put(xmlMsgLengthBytes);
tempOutputByteBuffer.put(messageBytes);
return tempOutputByteBuffer.array();
}
public static byte[] boatLocation(BoatLocation boatLocation){
int messageVersionNumber = 0b1;
byte[] time = longToBytes(boatLocation.getTime(), 6);
byte[] sourceID = intToBytes(boatLocation.getSourceID(), 4);
byte[] seqNum = longToBytes(boatLocation.getSequenceNumber(), 4);
byte[] deviceType = intToBytes(boatLocation.getDeviceType(), 1);
byte[] latitude = intToBytes(boatLocation.getLatitude(), 4);
byte[] longitude = intToBytes(boatLocation.getLongitude(), 4);
byte[] altitude = intToBytes(boatLocation.getAltitude(), 4);
byte[] heading = intToBytes(boatLocation.getHeading(), 2);
byte[] pitch = intToBytes(boatLocation.getPitch(), 2);
byte[] roll = intToBytes(boatLocation.getRoll(), 2);
byte[] boatSpeed = intToBytes(boatLocation.getBoatSpeed(), 2);
byte[] cog = intToBytes(boatLocation.getBoatCOG(), 2);
byte[] sog = intToBytes(boatLocation.getBoatSOG(), 2);
byte[] apparentWindSpeed = intToBytes(boatLocation.getApparentWindSpeed(), 2);
byte[] apparentWindAngle = intToBytes(boatLocation.getApparentWindAngle(), 2);
byte[] trueWindSpeed = intToBytes(boatLocation.getTrueWindSpeed(), 2);
byte[] trueWindDirection = intToBytes(boatLocation.getTrueWindDirection(), 2);
byte[] trueWindAngle = intToBytes(boatLocation.getTrueWindAngle(), 2);
byte[] currentDrift = intToBytes(boatLocation.getCurrentDrift(), 2);
byte[] currentSet = intToBytes(boatLocation.getCurrentSet(), 2);
byte[] rudderAngle = intToBytes(boatLocation.getRudderAngle(), 2);
ByteBuffer result = ByteBuffer.allocate(56);
result.put(intToBytes(messageVersionNumber, 1));
result.put(time);
result.put(sourceID);
result.put(seqNum);
result.put(deviceType);
result.put(latitude);
result.put(longitude);
result.put(altitude);
result.put(heading);
result.put(pitch);
result.put(roll);
result.put(boatSpeed);
result.put(cog);
result.put(sog);
result.put(apparentWindSpeed);
result.put(apparentWindAngle);
result.put(trueWindSpeed);
result.put(trueWindDirection);
result.put(trueWindAngle);
result.put(currentDrift);
result.put(currentSet);
result.put(rudderAngle);
return result.array();
}
public static byte[] markRounding(int time, int ackNumber, int raceID, int sourceID, int boatStatus, int roundingSide, int markType, int markID){
int messageVersionNumber = 0b1;
byte[] byteTime = longToBytes(time, 6);
byte[] byteAck = intToBytes(ackNumber, 2);
byte[] byteRaceID = intToBytes(raceID, 4);
byte[] byteSourceID = intToBytes(sourceID, 4);
byte[] byteBoatStatus = intToBytes(boatStatus, 1);
byte[] byteRoundingSide = intToBytes(roundingSide, 1);
byte[] byteMarkType = intToBytes(markType, 1);
byte[] byteMarkID = intToBytes(markID, 1);
ByteBuffer result = ByteBuffer.allocate(21);
result.put(intToBytes(messageVersionNumber, 1));
result.put(byteTime);
result.put(byteAck);
result.put(byteRaceID);
result.put(byteSourceID);
result.put(byteBoatStatus);
result.put(byteRoundingSide);
result.put(byteMarkType);
result.put(byteMarkID);
return result.array();
}
public static byte[] courseWind(byte windID, ArrayList<CourseWind> courseWinds){
int messageVersionNumber = 0b1;
byte byteWindID = windID;
byte[] loopcount = intToBytes(courseWinds.size(), 1);
ByteBuffer result = ByteBuffer.allocate(3 + 20 * courseWinds.size());
result.put(intToBytes(messageVersionNumber, 1));
result.put(byteWindID);
result.put(loopcount);
for (CourseWind wind: courseWinds){
result.put(intToBytes(wind.getID(), 1));
result.put(longToBytes(wind.getTime(), 6));
result.put(intToBytes(wind.getRaceID(), 4));
result.put(intToBytes(wind.getWindDirection(), 2));
result.put(intToBytes(wind.getWindSpeed(), 2));
result.put(intToBytes(wind.getBestUpwindAngle(), 2));
result.put(intToBytes(wind.getBestDownwindAngle(), 2));
result.put(intToBytes(wind.getFlags(), 1));
}
return result.array();
byte[] encodedMessage = encoder.encode(message);
return encodedMessage;
}
public static byte[] averageWind(int time, int rawPeriod, int rawSampleSpeed, int period2, int speed2, int period3, int speed3, int period4, int speed4){
int messageVersionNumber = 0b1;
byte[] byteTime = longToBytes(time,6);
byte[] byteRawPeriod = intToBytes(rawPeriod, 2);
byte[] byteRawSpeed = intToBytes(rawSampleSpeed, 2);
byte[] bytePeriod2 = intToBytes(period2, 2);
byte[] byteSpeed2 = intToBytes(speed2, 2);
byte[] bytePeriod3 = intToBytes(period3, 2);
byte[] byteSpeed3 = intToBytes(speed3, 2);
byte[] bytePeriod4 = intToBytes(period4, 2);
byte[] byteSpeed4 = intToBytes(speed4, 2);
ByteBuffer result = ByteBuffer.allocate(23);
result.put(intToBytes(messageVersionNumber, 1));
result.put(byteTime);
result.put(byteRawPeriod);
result.put(byteRawSpeed);
result.put(bytePeriod2);
result.put(byteSpeed2);
result.put(bytePeriod3);
result.put(byteSpeed3);
result.put(bytePeriod4);
result.put(byteSpeed4);
return result.array();
/**
* Encodes a given messages, using a given ackNumber, and returns a binary message ready to be sent over-the-wire.
* @param message The message to send.
* @param ackNumber The ackNumber of the message.
* @return A binary message ready to be transmitted.
* @throws InvalidMessageException Thrown if the message cannot be encoded.
*/
public static byte[] encodeBinaryMessage(AC35Data message, int ackNumber) throws InvalidMessageException {
//Encodes the message.
byte[] encodedMessage = RaceVisionByteEncoder.encode(message);
//Encodes the full message with header.
BinaryMessageEncoder binaryMessageEncoder = new BinaryMessageEncoder(
message.getType(),
System.currentTimeMillis(),
ackNumber,
(short) encodedMessage.length,
encodedMessage );
return binaryMessageEncoder.getFullMessage();
}
public static byte[] boatActionMessage(BoatAction boatAction){
ByteBuffer boatActionMessage = ByteBuffer.allocate(1);
boatActionMessage.put(intToBytes(boatAction.getBoatAction(), 1));
byte [] result = boatActionMessage.array();
return result;
}
}
@@ -0,0 +1,46 @@
package network.MessageEncoders;
import network.Exceptions.InvalidMessageException;
import network.Messages.AC35Data;
import network.Messages.RequestToJoin;
import network.Utils.ByteConverter;
import java.nio.ByteBuffer;
/**
* This encoder can encode a {@link network.Messages.RequestToJoin} message.
*/
public class RequestToJoinEncoder implements MessageEncoder {
/**
* Constructor.
*/
public RequestToJoinEncoder() {
}
@Override
public byte[] encode(AC35Data message) throws InvalidMessageException {
try {
//Downcast.
RequestToJoin requestToJoin = (RequestToJoin) message;
ByteBuffer requestToJoinBuffer = ByteBuffer.allocate(4);
requestToJoinBuffer.put(ByteConverter.intToBytes(requestToJoin.getRequestType().getValue(), 4));
byte[] result = requestToJoinBuffer.array();
return result;
} catch (Exception e) {
throw new InvalidMessageException("Could not encode RequestToJoin message.", e);
}
}
}
@@ -0,0 +1,69 @@
package network.MessageEncoders;
import network.Exceptions.InvalidMessageException;
import network.Messages.AC35Data;
import network.Messages.XMLMessage;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import static network.Utils.ByteConverter.intToBytes;
import static network.Utils.ByteConverter.longToBytes;
/**
* This encoder can encode a {@link XMLMessage} message.
*/
public class XMLMessageEncoder implements MessageEncoder {
/**
* Constructor.
*/
public XMLMessageEncoder() {
}
@Override
public byte[] encode(AC35Data message) throws InvalidMessageException {
try {
//Downcast.
XMLMessage xmlMessage = (XMLMessage) message;
byte[] messageBytes = xmlMessage.getXmlMessage().getBytes(StandardCharsets.UTF_8);
//Message is 14 + xmlMessage.length bytes.
ByteBuffer tempOutputByteBuffer = ByteBuffer.allocate(14 + messageBytes.length);
//ackNumber converted to bytes
byte[] ackNumberBytes = intToBytes(xmlMessage.getAckNumber(), 2);
//Timestamp converted to bytes.
byte[] timestampBytes = longToBytes(xmlMessage.getTimeStamp(), 6);
//sequenceNumber converted to bytes
byte[] sequenceNumberBytes = intToBytes(xmlMessage.getSequenceNumber(), 2);
//xmlMsgLength converted to bytes
byte[] xmlMsgLengthBytes = intToBytes(xmlMessage.getXmlMsgLength(), 2);
tempOutputByteBuffer.put(xmlMessage.getVersionNumber());
tempOutputByteBuffer.put(ackNumberBytes);
tempOutputByteBuffer.put(timestampBytes);
tempOutputByteBuffer.put(xmlMessage.getXmlMsgSubType().getValue());
tempOutputByteBuffer.put(sequenceNumberBytes);
tempOutputByteBuffer.put(xmlMsgLengthBytes);
tempOutputByteBuffer.put(messageBytes);
return tempOutputByteBuffer.array();
} catch (Exception e) {
throw new InvalidMessageException("Could not encode XMLMessage message.", e);
}
}
}
@@ -0,0 +1,131 @@
package network.MessageRouters;
import network.Messages.AC35Data;
import network.Messages.Enums.MessageType;
import org.jetbrains.annotations.NotNull;
import shared.model.RunnableWithFramePeriod;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.BlockingQueue;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* This class routes {@link network.Messages.AC35Data} messages to an appropriate message controller.
*/
public class MessageRouter implements RunnableWithFramePeriod {
/**
* Incoming queue of messages.
*/
private BlockingQueue<AC35Data> incomingMessages;
/**
* The routing map, which maps from a {@link MessageType} to a message queue.
*/
private Map<MessageType, BlockingQueue<AC35Data>> routeMap = new HashMap<>();
/**
* The default routing queue.
* Messages without routes are sent here.
* Nothing by default, which means unrouted messages are discarded
*/
private Optional<BlockingQueue<AC35Data>> defaultRoute = Optional.empty();
/**
* Constructs a {@link MessageRouter} with a given incoming message queue.
* @param incomingMessages Incoming message queue to read from.
*/
public MessageRouter(BlockingQueue<AC35Data> incomingMessages) {
this.incomingMessages = incomingMessages;
}
/**
* Returns the queue the message router reads from.
* Place messages onto this queue to pass them to the router.
* @return Queue the message router reads from.
*/
public BlockingQueue<AC35Data> getIncomingMessageQueue() {
return incomingMessages;
}
/**
* Adds a route, which routes a given type of message to a given queue.
* @param messageType The message type to route.
* @param queue The queue to route messages to.
*/
public void addRoute(MessageType messageType, BlockingQueue<AC35Data> queue) {
routeMap.put(messageType, queue);
}
/**
* Removes the route for a given {@link MessageType}.
* @param messageType MessageType to remove route for.
*/
public void removeRoute(MessageType messageType) {
routeMap.remove(messageType);
}
/**
* Adds a given queue as the default route for any unrouted message types.
* @param queue Queue to use as default route.
*/
public void addDefaultRoute(@NotNull BlockingQueue<AC35Data> queue) {
defaultRoute = Optional.of(queue);
}
/**
* Removes the current default route, if it exists.
*/
public void removeDefaultRoute() {
defaultRoute = Optional.empty();
}
@Override
public void run() {
while (!Thread.interrupted()) {
try {
AC35Data message = incomingMessages.take();
BlockingQueue<AC35Data> queue = routeMap.get(message.getType());
if (queue != null) {
queue.put(message);
} else {
//No route. Use default.
BlockingQueue<AC35Data> defaultQueue = defaultRoute.orElse(null);
if (defaultQueue != null) {
defaultQueue.put(message);
}
}
} catch (InterruptedException e) {
Logger.getGlobal().log(Level.SEVERE, "MessageRouter: " + this + " was interrupted on thread: " + Thread.currentThread() + " while reading message.", e);
Thread.currentThread().interrupt();
}
}
}
}
@@ -0,0 +1,38 @@
package network.Messages;
import network.Messages.Enums.MessageType;
/**
* This is the message the client generates and sends to itself once the server has assigned a boat source ID with {@link JoinAcceptance}.
*/
public class AssignPlayerBoat extends AC35Data {
/**
* The source ID of the boat assigned to the client.
* 0 indicates they haven't been assigned a boat.
*/
private int sourceID = 0;
/**
* Constructs a AssignPlayerBoat message.
* @param sourceID The sourceID to assign to the client. 0 indicates no sourceID.
*/
public AssignPlayerBoat(int sourceID){
super(MessageType.ASSIGN_PLAYER_BOAT);
this.sourceID = sourceID;
}
/**
* Returns the source ID of the boat assigned to the client.
* @return The source ID of the boat assigned to the client.
*/
public int getSourceID() {
return sourceID;
}
}
@@ -4,33 +4,187 @@ package network.Messages;
import network.Messages.Enums.MessageType;
/**
* Created by fwy13 on 25/04/17.
* Represents an AverageWind message in the streaming API (see section 4.12).
*/
public class AverageWind extends AC35Data {
private int msgNum;
private long lngTime;
private int rawPeriod;
private int rawSpeed;
private int period2;
private int speed2;
private int period3;
private int speed3;
private int period4;
private int speed4;
/**
* The current version number for this message type.
*/
public static final byte currentMessageVersionNumber = 1;
/**
* The version number for this message.
*/
private byte messageVersionNumber;
/**
* Timestamp for when the measurement was taken. Milliseconds since unix epoch.
*/
private long time;
/**
* Raw sample rate period. Milliseconds.
*/
private long rawPeriod;
/**
* Raw wind speed, in knots.
*/
private double rawSpeedKnots;
/**
* Wind speed average period for second sample. Milliseconds.
*/
private long sampleTwoPeriod;
/**
* Wind speed of second sample. Knots.
*/
private double sampleTwoSpeedKnots;
/**
* Wind speed average period for third sample. Milliseconds.
*/
private long sampleThreePeriod;
/**
* Wind speed of third sample. Knots.
*/
private double sampleThreeSpeedKnots;
/**
* Wind speed average period for fourth sample. Milliseconds.
*/
private long sampleFourPeriod;
/**
* Wind speed of fourth sample. Knots.
*/
private double sampleFourSpeedKnots;
/**
* Creates an AverageWind message, with the given parameters.
* @param messageVersionNumber The version number of message.
* @param time The timestamp of the message.
* @param rawPeriod The period of the raw measurement. Milliseconds.
* @param rawSpeedKnots The speed of the raw measurement. Knots.
* @param sampleTwoPeriod The period of the second measurement. Milliseconds.
* @param sampleTwoSpeedKnots The speed of the second measurement. Knots.
* @param sampleThreePeriod The period of the third measurement. Milliseconds.
* @param sampleThreeSpeedKnots The speed of the third measurement. Knots.
* @param sampleFourPeriod The period of the fourth measurement. Milliseconds.
* @param sampleFourSpeedKnots The speed of the fourth measurement. Knots.
*/
public AverageWind(
byte messageVersionNumber,
long time,
long rawPeriod,
double rawSpeedKnots,
long sampleTwoPeriod,
double sampleTwoSpeedKnots,
long sampleThreePeriod,
double sampleThreeSpeedKnots,
long sampleFourPeriod,
double sampleFourSpeedKnots ) {
public AverageWind(int msgNum, long lngTime, int rawPeriod, int rawSpeed, int period2, int speed2, int period3, int speed3, int period4, int speed4){
super(MessageType.AVGWIND);
this.msgNum = msgNum;
this.lngTime = lngTime;
this.messageVersionNumber = messageVersionNumber;
this.time = time;
this.rawPeriod = rawPeriod;
this.rawSpeed = rawSpeed;
this.period2 = period2;
this.speed2 = speed2;
this.period3 = period3;
this.speed3 = speed3;
this.period4 = period4;
this.speed4 = speed4;
this.rawSpeedKnots = rawSpeedKnots;
this.sampleTwoPeriod = sampleTwoPeriod;
this.sampleTwoSpeedKnots = sampleTwoSpeedKnots;
this.sampleThreePeriod = sampleThreePeriod;
this.sampleThreeSpeedKnots = sampleThreeSpeedKnots;
this.sampleFourPeriod = sampleFourPeriod;
this.sampleFourSpeedKnots = sampleFourSpeedKnots;
}
/**
* Returns the version number of this message.
* @return Message version number.
*/
public byte getMessageVersionNumber() {
return messageVersionNumber;
}
/**
* Returns the timestamp of this message.
* @return Timestamp of this message. Milliseconds since unix epoch.
*/
public long getTime() {
return time;
}
/**
* Returns the period of time over which the raw sample was done. Milliseconds.
* @return Raw sample's period.
*/
public long getRawPeriod() {
return rawPeriod;
}
/**
* Returns the wind speed of the raw sample. Knots.
* @return Wind speed of raw sample. Knots
*/
public double getRawSpeedKnots() {
return rawSpeedKnots;
}
/**
* Returns the period of time over which the second sample was done. Milliseconds.
* @return Second sample's period.
*/
public long getSampleTwoPeriod() {
return sampleTwoPeriod;
}
/**
* Returns the wind speed of the second sample. Knots.
* @return Wind speed of second sample. Knots
*/
public double getSampleTwoSpeedKnots() {
return sampleTwoSpeedKnots;
}
/**
* Returns the period of time over which the third sample was done. Milliseconds.
* @return Third sample's period.
*/
public long getSampleThreePeriod() {
return sampleThreePeriod;
}
/**
* Returns the wind speed of the third sample. Knots.
* @return Wind speed of third sample. Knots
*/
public double getSampleThreeSpeedKnots() {
return sampleThreeSpeedKnots;
}
/**
* Returns the period of time over which the fourth sample was done. Milliseconds.
* @return Fourth sample's period.
*/
public long getSampleFourPeriod() {
return sampleFourPeriod;
}
/**
* Returns the wind speed of the fourth sample. Knots.
* @return Wind speed of fourth sample. Knots
*/
public double getSampleFourSpeedKnots() {
return sampleFourSpeedKnots;
}
}
@@ -4,19 +4,51 @@ import network.Messages.Enums.BoatActionEnum;
import network.Messages.Enums.MessageType;
/**
* Created by David on 10/07/2017.
* Represents a BoatAction message.
*/
public class BoatAction extends AC35Data {
private byte boatAction;
/**
* The action for this message.
*/
private BoatActionEnum boatAction;
/**
* The source ID of the boat this action relates to.
*/
private int sourceID = 0;
/**
* Constructs a BoatActon message with a given action.
* @param boatAction Action to use.
*/
public BoatAction(BoatActionEnum boatAction){
super(MessageType.BOATACTION);
this.boatAction = boatAction.getValue();
this.boatAction = boatAction;
}
public byte getBoatAction() {
/**
* Returns the action for this message.
* @return The action for this message.
*/
public BoatActionEnum getBoatAction() {
return boatAction;
}
}
/**
* Returns the boat source ID for this message.
* @return The source ID for this message.
*/
public int getSourceID() {
return sourceID;
}
/**
* Sets the boat source ID for this message.
* @param sourceID The source for this message.
*/
public void setSourceID(int sourceID) {
this.sourceID = sourceID;
}
}
@@ -1,130 +1,188 @@
package network.Messages;
import network.Messages.Enums.BoatLocationDeviceEnum;
import network.Messages.Enums.MessageType;
import network.Utils.AC35UnitConverter;
import shared.model.Constants;
import shared.model.Azimuth;
import shared.model.Bearing;
import static network.Utils.AC35UnitConverter.convertGPS;
import static network.Utils.AC35UnitConverter.convertGPSToInt;
import static network.Utils.AC35UnitConverter.unpackGPS;
/**
* Represents the information in a boat location message (AC streaming spec: 4.9).
*/
public class BoatLocation extends AC35Data {
//TODO move these to an enum.
public static final byte Unknown = 0;
public static final byte RacingYacht = 1;
public static final byte CommitteeBoat = 2;
public static final byte Mark = 3;
public static final byte Pin = 4;
public static final byte ChaseBoat = 5;
public static final byte MedicalBoat = 6;
public static final byte MarshallBoat = 7;
public static final byte UmpireBoat = 8;
public static final byte UmpireSoftwareApplication = 9;
public static final byte PrincipalRaceOfficerApplication = 10;
public static final byte WeatherStation = 11;
public static final byte Helicopter = 12;
public static final byte DataProcessingApplication = 13;
///Version number of the message - is always 1.
private byte messageVersionNumber = 1;
///Time of the event - milliseconds since jan 1 1970. Proper type is 6 byte int.
/**
* 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.
/**
* Source ID of the boat.
*/
private int sourceID;
///Sequence number of the message.
/**
* Sequence number of the message.
*/
private long sequenceNumber;
///Device type of the message (physical source of the message).
private byte deviceType;
///Latitude of the boat.
private int latitude;
///Longitude of the boat.
private int longitude;
/**
* Device type of the message (physical source of the message).
*/
private BoatLocationDeviceEnum deviceType;
///Altitude of the boat.
/**
* Latitude of the boat.
*/
private double latitude;
/**
* Longitude of the boat.
*/
private double longitude;
/**
* Altitude of the boat.
*/
private int altitude;
///Heading of the boat. Clockwise, 0 = north. Proper type is unsigned 2 byte int.
private int heading;
/**
* Heading of the boat. Clockwise, 0 = north. Proper type is unsigned 2 byte int.
*/
private Bearing heading;
///Pitch of the boat.
/**
* Pitch of the boat.
*/
private short pitch;
///Roll of the boat.
/**
* Roll of the boat.
*/
private short roll;
///Speed of the boat. Proper type is unsigned 2 byte int. millimeters per second.
private int boatSpeed;
///Course over ground (COG) of the boat. Proper type is unsigned 2 byte int.
private int boatCOG;
///Speed over ground (SOG) of the boat. Proper type is unsigned 2 byte int. millimeters per second.
private int boatSOG;
///Apparent wind speed at time of event. Proper type is unsigned 2 byte int. millimeters per second.
private int apparentWindSpeed;
///Apparent wind angle at time of the event. Wind over starboard = positive.
private short apparentWindAngle;
///True wind speed. Proper type is unsigned 2 byte int. millimeters per second.
private int trueWindSpeed;
///True wind direction. Proper type is unsigned 2 byte int. 0x0000 = North, etc..
private int trueWindDirection;
///True wind angle. Clockwise compass direction, 0 = north.
private short trueWindAngle;
///Current drift. Proper type is unsigned 2 byte int. millimeters per second.
private int currentDrift;
///Current set. Proper type is unsigned 2 byte int. Clockwise compass direction, 0 = north.
private int currentSet;
///Rudder angle. Positive is rudder set to turn yacht to port.
private short rudderAngle;
/**
* Ctor. Default.
* Speed of the boat, in knots.
*/
public BoatLocation() {
super(MessageType.BOATLOCATION);
}
private double boatSpeedKnots;
/**
* Ctor, with all parameters.
* Course over ground (COG) of the boat.
*/
private Bearing boatCOG;
/**
* Speed over ground (SOG) of the boat, in knots.
*/
private double boatSOGKnots;
/**
* Apparent wind speed at time of event. Proper type is unsigned 2 byte int. millimeters per second.
*/
private double apparentWindSpeedKnots;
/**
* Apparent wind angle at time of the event. Wind over starboard = positive.
*/
private Azimuth apparentWindAngle;
/**
* True wind speed, in knots.
*/
private double trueWindSpeedKnots;
/**
* True wind direction.
*/
private Bearing trueWindDirection;
/**
* True wind angle. Clockwise compass direction, 0 = north.
*/
private Azimuth trueWindAngle;
/**
* Current drift, in knots.
*/
private double currentDriftKnots;
/**
* Current set.
*/
private Bearing currentSet;
/**
* Rudder angle. Positive is rudder set to turn yacht to port.
*/
private Azimuth rudderAngle;
/**
* Constructs a BoatLocation message with the given parameters.
*
* @param messageVersionNumber message number
* @param time time of message
* @param sourceID id of boat
* @param sequenceNumber number of boat message
* @param deviceType type of boat
* @param deviceType The source of the BoatLocation message.
* @param latitude lat of boat
* @param longitude lon of boat
* @param altitude altitude of boat
* @param heading heading of boat
* @param pitch pitch of boat
* @param roll roll of boat
* @param boatSpeed boats speed
* @param boatSpeedKnots boats speed
* @param boatCOG boat cog
* @param boatSOG boat sog
* @param apparentWindSpeed wind speed
* @param boatSOGKnots boat sog
* @param apparentWindSpeedKnots wind speed
* @param apparentWindAngle wind angle
* @param trueWindSpeed true wind speed
* @param trueWindSpeedKnots true wind speed
* @param trueWindDirection true wind direction
* @param trueWindAngle true wind angle
* @param currentDrift current drift
* @param currentDriftKnots current drift
* @param currentSet current set
* @param rudderAngle rudder angle
*/
public BoatLocation(byte messageVersionNumber, long time, int sourceID, long sequenceNumber, byte deviceType, int latitude, int longitude, int altitude, int heading, short pitch, short roll, int boatSpeed, int boatCOG, int boatSOG, int apparentWindSpeed, short apparentWindAngle, int trueWindSpeed, int trueWindDirection, short trueWindAngle, int currentDrift, int currentSet, short rudderAngle) {
public BoatLocation(
byte messageVersionNumber,
long time,
int sourceID,
long sequenceNumber,
BoatLocationDeviceEnum deviceType,
double latitude,
double longitude,
int altitude,
Bearing heading,
short pitch,
short roll,
double boatSpeedKnots,
Bearing boatCOG,
double boatSOGKnots,
double apparentWindSpeedKnots,
Azimuth apparentWindAngle,
double trueWindSpeedKnots,
Bearing trueWindDirection,
Azimuth trueWindAngle,
double currentDriftKnots,
Bearing currentSet,
Azimuth rudderAngle ) {
super(MessageType.BOATLOCATION);
this.messageVersionNumber = messageVersionNumber;
@@ -138,348 +196,251 @@ public class BoatLocation extends AC35Data {
this.heading = heading;
this.pitch = pitch;
this.roll = roll;
this.boatSpeed = boatSpeed;
this.boatSpeedKnots = boatSpeedKnots;
this.boatCOG = boatCOG;
this.boatSOG = boatSOG;
this.apparentWindSpeed = apparentWindSpeed;
this.boatSOGKnots = boatSOGKnots;
this.apparentWindSpeedKnots = apparentWindSpeedKnots;
this.apparentWindAngle = apparentWindAngle;
this.trueWindSpeed = trueWindSpeed;
this.trueWindSpeedKnots = trueWindSpeedKnots;
this.trueWindDirection = trueWindDirection;
this.trueWindAngle = trueWindAngle;
this.currentDrift = currentDrift;
this.currentDriftKnots = currentDriftKnots;
this.currentSet = currentSet;
this.rudderAngle = rudderAngle;
}
public BoatLocation(int sourceID, double lat, double lon, long sequenceNumber, double heading, double boatSpeed, long time) {
super(MessageType.BOATLOCATION);
this.messageVersionNumber = (byte) 1;
this.time = time;
this.sourceID = sourceID;
this.sequenceNumber = sequenceNumber;
this.deviceType = 1;
this.latitude = convertGPSToInt(lat);
this.longitude = convertGPSToInt(lon);
this.altitude = 0;
this.heading = convertHeadingDoubleToInt(heading);
this.pitch = 0;
this.roll = 0;
this.boatSpeed = convertBoatSpeedDoubleToInt(boatSpeed);
this.boatCOG = 0;
this.boatSOG = convertBoatSpeedDoubleToInt(boatSpeed);
this.apparentWindSpeed = 0;
this.apparentWindAngle = 0;
this.trueWindSpeed = 0;
this.trueWindDirection = 0;
this.trueWindAngle = 0;
this.currentDrift = 0;
this.currentSet = 0;
this.rudderAngle = 0;
public BoatLocation(
int sourceID,
double lat,
double lon,
long sequenceNumber,
BoatLocationDeviceEnum deviceType,
Bearing heading,
double boatSpeedKnots,
long time ) {
this(
BoatLocation.currentMessageVersionNumber,
time,
sourceID,
sequenceNumber,
deviceType,
lat,
lon,
0,
heading,
(short) 0,
(short) 0,
boatSpeedKnots,
heading,
boatSpeedKnots,
0,
Azimuth.fromDegrees(0),
0,
Bearing.fromDegrees(0),
Azimuth.fromDegrees(0),
0,
Bearing.fromDegrees(0),
Azimuth.fromDegrees(0) );
}
//Getters and setters for message properties.
/**
* Converts a double representing a latitude or longitude coordinate to an int, as required by the streaming spec format.
*
* @param coordinate Latitude or longitude to convert. Double.
* @return int representation of coordinate.
* Returns the version number of the message.
* @return The version number of the message.
*/
public static int convertCoordinateDoubleToInt(double coordinate) {
int coordinateInt = (int) ((coordinate / 180.0) * 2147483648.0);
return coordinateInt;
}
/**
* Converts an int representing a latitude or longitude coordinate to a double, as required by the streaming spec format.
*
* @param coordinate Latitude or longitude to convert. int.
* @return double representation of coordinate.
*/
public static double convertCoordinateIntToDouble(int coordinate) {
double coordinateDouble = (double) ((coordinate * 180.0) / 2147483648.0);
return coordinateDouble;
}
/**
* Converts an int representing a heading to a double, as required by the streaming spec format.
*
* @param heading Heading to convert. int.
* @return double representation of heading.
*/
public static double convertHeadingIntToDouble(int heading) {
double headingDouble = (double) ((heading * 360.0) / 65536.0);
return headingDouble;
}
/**
* Converts a double representing a heading to an int, as required by the streaming spec format.
*
* @param heading Heading to convert. double.
* @return int representation of heading.
*/
public static int convertHeadingDoubleToInt(double heading) {
int headingInt = (int) ((heading * 65536.0) / 360.0);
return headingInt;
}
/**
* Converts a short representing the wind's true angle to a double, as required by the streaming spec format.
*
* @param angle Angle to convert. short.
* @return double representation of heading.
*/
public static double convertTrueWindAngleShortToDouble(short angle) {
double angleDouble = (double) ((angle * 180.0) / 32768.0);
return angleDouble;
}
/**
* Converts a double representing the wind's true angle to a short, as required by the streaming spec format.
*
* @param angle Angle to convert. double.
* @return short representation of heading.
*/
public static short convertTrueWindAngleDoubleToShort(double angle) {
short angleShort = (short) ((angle / 180.0) * 32768.0);
return angleShort;
}
/**
* Converts a double representing the speed of a boat in knots to an int in millimeters per second, as required by the streaming spec format.
*
* @param speed Speed in knots, stored as a double.
* @return Speed in millimeters per second, stored as an int (using only the two least significant bytes).
*/
public static int convertBoatSpeedDoubleToInt(double speed) {
//Calculate millimeters per second.
double millimetersPerSecond = speed * Constants.KnotsToMMPerSecond;
//Convert to an int.
int millimetersPerSecondInt = (int) Math.round(millimetersPerSecond);
return millimetersPerSecondInt;
}
/**
* Converts an int representing the speed of a boat in millimeters per second to a double in knots, as required by the streaming spec format.
*
* @param speed Speed in millimeters per second, stored as an int.
* @return Speed in knots, stored as a double.
*/
public static double convertBoatSpeedIntToDouble(int speed) {
//Calculate knots.
double knots = speed / Constants.KnotsToMMPerSecond;
return knots;
}
public byte getMessageVersionNumber() {
return messageVersionNumber;
}
public void setMessageVersionNumber(byte messageVersionNumber) {
this.messageVersionNumber = messageVersionNumber;
}
/**
* Returns the time that this message was generated at.
* @return Time message was generated at, in milliseconds since unix epoch.
*/
public long getTime() {
return time;
}
public void setTime(long time) {
this.time = time;
}
/**
* Returns the sourceID of the boat this message relates to.
* @return SourceID of the boat this message relates to.
*/
public int getSourceID() {
return sourceID;
}
public void setSourceID(int sourceID) {
this.sourceID = sourceID;
}
/**
* Returns the sequence number of this message.
* @return The sequence number of the message.
*/
public long getSequenceNumber() {
return sequenceNumber;
}
public void setSequenceNumber(long sequenceNumber) {
this.sequenceNumber = sequenceNumber;
}
public byte getDeviceType() {
/**
* Returns the device source of this message.
* @return The device source of this message.
*/
public BoatLocationDeviceEnum getDeviceType() {
return deviceType;
}
public void setDeviceType(byte deviceType) {
this.deviceType = deviceType;
}
public int getLatitude() {
/**
* Returns the latitude, in degrees, that the boat is located at.
* @return Latitude, in degrees, of boat.
*/
public double getLatitude() {
return latitude;
}
public void setLatitude(int latitude) {
this.latitude = latitude;
}
public int getLongitude() {
/**
* Returns the longitude, in degrees, that the boat is located at.
* @return Longitude, in degrees, of boat.
*/
public double getLongitude() {
return longitude;
}
public double getLatitudeDouble(){
return convertGPS(this.latitude);
}
public double getLongitudeDouble(){
return convertGPS(this.longitude);
}
public void setLongitude(int longitude) {
this.longitude = longitude;
}
/**
* Returns the altitude of the boat.
* @return The altitude of the boat.
*/
public int getAltitude() {
return altitude;
}
public void setAltitude(int altitude) {
this.altitude = altitude;
}
public int getHeading() {
/**
* Returns the current heading/bearing of the boat.
* @return Heading of the boat.
*/
public Bearing getHeading() {
return heading;
}
public void setHeading(int heading) {
this.heading = heading;
}
/**
* Returns the current pitch of the boat.
* @return Pitch of the boat.
*/
public short getPitch() {
return pitch;
}
public void setPitch(short pitch) {
this.pitch = pitch;
}
/**
* Returns the current roll of the boat.
* @return Roll of the boat.
*/
public short getRoll() {
return roll;
}
public void setRoll(short roll) {
this.roll = roll;
/**
* Returns the current boat speed, in knots.
* @return Current boat speed, in knots.
*/
public double getBoatSpeedKnots() {
return boatSpeedKnots;
}
public int getBoatSpeed() {
return boatSpeed;
}
public void setBoatSpeed(int boatSpeed) {
this.boatSpeed = boatSpeed;
}
public int getBoatCOG() {
/**
* Returns the boat's Course Over Ground.
* @return Boat's COG.
*/
public Bearing getBoatCOG() {
return boatCOG;
}
public void setBoatCOG(int boatCOG) {
this.boatCOG = boatCOG;
/**
* Returns the boats Speed Over Ground, in knots.
* @return Boat's SOG.
*/
public double getBoatSOGKnots() {
return boatSOGKnots;
}
public int getBoatSOG() {
return boatSOG;
/**
* Returns the apparent wind speed, in knots, at the boat.
* @return Wind speed, in knots, at the boat.
*/
public double getApparentWindSpeedKnots() {
return apparentWindSpeedKnots;
}
public void setBoatSOG(int boatSOG) {
this.boatSOG = boatSOG;
}
public int getApparentWindSpeed() {
return apparentWindSpeed;
}
public void setApparentWindSpeed(int apparentWindSpeed) {
this.apparentWindSpeed = apparentWindSpeed;
}
public short getApparentWindAngle() {
/**
* Returns the apparent wind angle at the boat.
* @return Wind angle at the boat.
*/
public Azimuth getApparentWindAngle() {
return apparentWindAngle;
}
public void setApparentWindAngle(short apparentWindAngle) {
this.apparentWindAngle = apparentWindAngle;
/**
* Returns the true wind speed, in knots.
* @return True wind speed, in knots.
*/
public double getTrueWindSpeedKnots() {
return trueWindSpeedKnots;
}
public int getTrueWindSpeed() {
return trueWindSpeed;
}
public void setTrueWindSpeed(int trueWindSpeed) {
this.trueWindSpeed = trueWindSpeed;
}
public int getTrueWindDirection()
/**
* Returns the true wind direction.
* @return True wind direction.
*/
public Bearing getTrueWindDirection()
{
return trueWindDirection;
}
public void setTrueWindDirection(int trueWindDirection)
{
this.trueWindDirection = trueWindDirection;
}
public short getTrueWindAngle() {
/**
* Returns the true wind angle.
* @return True wind angle.
*/
public Azimuth getTrueWindAngle() {
return trueWindAngle;
}
public void setTrueWindAngle(short trueWindAngle) {
this.trueWindAngle = trueWindAngle;
/**
* Returns the current drift of the boat, in knots.
* @return Current drift, in knots.
*/
public double getCurrentDriftKnots() {
return currentDriftKnots;
}
public int getCurrentDrift() {
return currentDrift;
}
public void setCurrentDrift(int currentDrift) {
this.currentDrift = currentDrift;
}
public int getCurrentSet() {
/**
* Returns the current set of the boat.
* @return Current set of the boat.
*/
public Bearing getCurrentSet() {
return currentSet;
}
public void setCurrentSet(int currentSet) {
this.currentSet = currentSet;
}
public short getRudderAngle() {
/**
* Returns the current rudder angle of the boat.
* @return Current rudder angle of the boat.
*/
public Azimuth getRudderAngle() {
return rudderAngle;
}
public void setRudderAngle(short rudderAngle) {
this.rudderAngle = rudderAngle;
}
public double getHeadingDegrees(){
return AC35UnitConverter.convertHeading(getHeading());
}
public double getTrueWindAngleDegrees(){
return AC35UnitConverter.convertTrueWindAngle(getTrueWindAngle());
}
@Override
public String toString() {
@@ -519,28 +480,28 @@ public class BoatLocation extends AC35Data {
builder.append(this.getRoll());
builder.append("\nBoat speed (mm/sec): ");
builder.append(this.getBoatSpeed());
builder.append(this.getBoatSpeedKnots());
builder.append("\nBoat COG: ");
builder.append(this.getBoatCOG());
builder.append("\nBoat SOG: ");
builder.append(this.getBoatSOG());
builder.append(this.getBoatSOGKnots());
builder.append("\nApparent wind speed: ");
builder.append(this.getApparentWindSpeed());
builder.append(this.getApparentWindSpeedKnots());
builder.append("\nApparent wind angle: ");
builder.append(this.getApparentWindAngle());
builder.append("\nTrue wind speed: ");
builder.append(this.getTrueWindSpeed());
builder.append(this.getTrueWindSpeedKnots());
builder.append("\nTrue wind angle: ");
builder.append(this.getTrueWindAngle());
builder.append("\nCurrent drift: ");
builder.append(this.getCurrentDrift());
builder.append(this.getCurrentDriftKnots());
builder.append("\nCurrent set: ");
builder.append(this.getCurrentSet());
@@ -5,22 +5,62 @@ import network.Messages.Enums.BoatStatusEnum;
import network.Utils.ByteConverter;
/**
* Created by hba56 on 23/04/17.
* Represents the information in a BoatStatus message, which is contained inside a RaceStatus message (AC streaming spec: 4.2).
*/
public class BoatStatus {
/**
* The sourceID of the boat.
*/
private int sourceID;
private byte boatStatus;
/**
* The status of the boat.
*/
private BoatStatusEnum boatStatus;
/**
* The leg number that the boat is on.
*/
private byte legNumber;
/**
* The number of penalties awarded to the boat.
*/
private byte numPenaltiesAwarded;
/**
* The number of penalties served by the boat.
*/
private byte numPenaltiesServed;
/**
* The time at which it is estimated the boat will reach the next mark.
* Milliseconds since unix epoch.
*/
private long estTimeAtNextMark;
/**
* The time at which it is estimated the boat will finish the race.
* Milliseconds since unix epoch.
*/
private long estTimeAtFinish;
public BoatStatus(int sourceID, byte boatStatus, byte legNumber, byte numPenaltiesAwarded, byte numPenaltiesServed, long estTimeAtNextMark, long estTimeAtFinish) {
/**
* Constructs a BoatStatus message with the given parameters.
* @param sourceID The sourceID of the boat.
* @param boatStatus The status of the boat.
* @param legNumber The leg number the boat is on.
* @param numPenaltiesAwarded The number of penalties awarded to the boat.
* @param numPenaltiesServed The number of penalties served by the boat.
* @param estTimeAtNextMark The estimated time at which the boat will reach the next mark.
* @param estTimeAtFinish The estimated time at which the boat will finish the race.
*/
public BoatStatus(int sourceID, BoatStatusEnum boatStatus, int legNumber, byte numPenaltiesAwarded, byte numPenaltiesServed, long estTimeAtNextMark, long estTimeAtFinish) {
this.sourceID = sourceID;
this.boatStatus = boatStatus;
this.legNumber = legNumber;
this.legNumber = ByteConverter.intToBytes(legNumber, 1)[0];
this.numPenaltiesAwarded = numPenaltiesAwarded;
this.numPenaltiesServed = numPenaltiesServed;
this.estTimeAtNextMark = estTimeAtNextMark;
@@ -28,41 +68,77 @@ public class BoatStatus {
}
public BoatStatus(int sourceID, BoatStatusEnum boatStatusEnum, int legNum, long estTimeAtNextMark) {
this.sourceID = sourceID;
this.boatStatus = boatStatusEnum.getValue();
this.legNumber = ByteConverter.intToBytes(legNum)[0];
this.numPenaltiesAwarded = 0;
this.numPenaltiesServed = 0;
this.estTimeAtFinish = 0;
this.estTimeAtNextMark = estTimeAtNextMark;
/**
* Constructs a BoatStatus message with the given parameters. Sets penalties to zero, and time at finish to zero.
* @param sourceID The sourceID of the boat.
* @param boatStatus The status of the boat.
* @param legNumber The leg number the boat is on.
* @param estTimeAtNextMark The estimated time at which the boat will reach the next mark.
*/
public BoatStatus(int sourceID, BoatStatusEnum boatStatus, int legNumber, long estTimeAtNextMark) {
this(
sourceID,
boatStatus,
legNumber,
(byte) 0,
(byte) 0,
estTimeAtNextMark,
0 );
}
/**
* Returns the sourceID of the boat.
* @return The sourceID of the boat.
*/
public int getSourceID() {
return sourceID;
}
public byte getBoatStatus() {
/**
* Returns the status of the boat.
* @return The status of the boat.
*/
public BoatStatusEnum getBoatStatus() {
return boatStatus;
}
/**
* Returns the leg number of boat is on.
* @return The leg number of boat is on.
*/
public byte getLegNumber() {
return legNumber;
}
/**
* Returns the number of penalties awarded to the boat.
* @return Number of penalties awarded to boat.
*/
public byte getNumPenaltiesAwarded() {
return numPenaltiesAwarded;
}
/**
* Returns the number of penalties served by the boat.
* @return The number of penalties served by the boat.
*/
public byte getNumPenaltiesServed() {
return numPenaltiesServed;
}
/**
* Returns the time at which it is estimated the boat will reach the next mark. Milliseconds since unix epoch.
* @return Time at which boat will reach next mark.
*/
public long getEstTimeAtNextMark() {
return estTimeAtNextMark;
}
/**
* Returns the time at which it is estimated the boat will finish the race. Milliseconds since unix epoch.
* @return Time at which boat will finish the race.
*/
public long getEstTimeAtFinish() {
return estTimeAtFinish;
}
@@ -2,57 +2,132 @@ package network.Messages;
import network.Messages.Enums.MessageType;
import shared.model.Bearing;
/**
* Created by fwy13 on 21/04/17.
* Contains a single CourseWind record.
* A CourseWinds message contains one or more CourseWind messages.
*/
public class CourseWind extends AC35Data {
private int ID, raceID, windDirection, windSpeed, bestUpwindAngle, bestDownwindAngle, flags;
/**
* The ID for this wind source.
*/
private int ID;
/**
* The time the wind was captured at. Milliseconds since unix epoch.
*/
private long time;
public CourseWind(int ID, long time, int raceID, int windDirection, int windSpeed, int bestUpwindAngle, int bestDownwindAngle,
int flags){
/**
* The ID of the race this applies to.
* 0 means it isn't race specific.
*/
private int raceID;
/**
* Direction of the wind.
*/
private Bearing windDirection;
/**
* The speed of the wind, in knots.
*/
private double windSpeedKnots;
/**
* Optimum upwind sailing angle.
*/
private Bearing bestUpwindAngle;
/**
* Optimum downwind sailing angle.
*/
private Bearing bestDownwindAngle;
/**
* Various flags which determine which values are valid.
*/
private short flags;
public CourseWind(int ID, long time, int raceID, Bearing windDirection, double windSpeedKnots, Bearing bestUpwindAngle, Bearing bestDownwindAngle, short flags) {
super(MessageType.COURSEWIND);
this.ID = ID;
this.time = time;
this.raceID = raceID;
this.windDirection = windDirection;
this.windSpeed = windSpeed;
this.windSpeedKnots = windSpeedKnots;
this.bestUpwindAngle = bestUpwindAngle;
this.bestDownwindAngle = bestDownwindAngle;
this.flags = flags;
}
/**
* Returns the ID of the wind source.
* @return ID of the wind source.
*/
public int getID() {
return ID;
}
/**
* Returns the time that this was captured at. Milliseconds since unix epoch.
* @return Time this wind was captured at.
*/
public long getTime() {
return time;
}
/**
* Returns the ID of the race this wind source belongs to. 0 means any race.
* @return ID of the race this belongs to.
*/
public int getRaceID() {
return raceID;
}
public int getWindDirection() {
/**
* Returns the direction of the wind.
* @return The direction of the wind.
*/
public Bearing getWindDirection() {
return windDirection;
}
public int getWindSpeed() {
return windSpeed;
/**
* Returns the wind speed, in knots.
* @return Wind speed, in knots.
*/
public double getWindSpeedKnots() {
return windSpeedKnots;
}
public int getBestUpwindAngle() {
/**
* Returns the best upwind sailing angle.
* @return Best upwind sailing angle.
*/
public Bearing getBestUpwindAngle() {
return bestUpwindAngle;
}
public int getBestDownwindAngle() {
/**
* Returns the best downwind sailing angle.
* @return The best downwind sailing angle.
*/
public Bearing getBestDownwindAngle() {
return bestDownwindAngle;
}
/**
* Returns various flags which determine which values are valid.
* @return Flag which determines which values are valid.
*/
public int getFlags() {
return flags;
}
public long getTime() {
return time;
}
}
@@ -6,19 +6,67 @@ import network.Messages.Enums.MessageType;
import java.util.List;
/**
* Created by fwy13 on 25/04/17.
* Represents the information in a CourseWind message (AC streaming spec: 4.11).
*/
public class CourseWinds extends AC35Data {
private int msgVerNum;
private int selectedWindID;
/**
* The current version number for this message type.
*/
public static final byte currentMessageVersionNumber = 1;
/**
* The version number of this message.
*/
private byte messageVersionNumber;
/**
* The ID of the wind source currently selected.
*/
private byte selectedWindID;
/**
* A list of wind sources.
*/
private List<CourseWind> courseWinds;
public CourseWinds(int msgVerNum, int selectedWindID, List<CourseWind> courseWinds){
/**
* Constructs a CourseWinds with given parameters.
* @param messageVersionNumber The version number of the message.
* @param selectedWindID The selected wind ID.
* @param courseWinds A list of wind sources.
*/
public CourseWinds(byte messageVersionNumber, byte selectedWindID, List<CourseWind> courseWinds) {
super(MessageType.COURSEWIND);
this.msgVerNum = msgVerNum;
this.messageVersionNumber = messageVersionNumber;
this.selectedWindID = selectedWindID;
this.courseWinds = courseWinds;
}
/**
* Returns the version number of this message.
* @return Version number of this message.
*/
public byte getMessageVersionNumber() {
return messageVersionNumber;
}
/**
* Returns the ID of the selected wind source.
* @return ID of the selected wind source.
*/
public byte getSelectedWindID() {
return selectedWindID;
}
/**
* Returns the list of wind sources.
* @return List of wind sources.
*/
public List<CourseWind> getCourseWinds() {
return courseWinds;
}
}
@@ -8,6 +8,10 @@ import java.util.Map;
*/
public enum BoatActionEnum {
NOT_A_STATUS(-1),
/**
* Autopilot = auto VMG.
*/
AUTO_PILOT(1),
SAILS_IN(2),
SAILS_OUT(3),
@@ -68,4 +72,4 @@ public enum BoatActionEnum {
}
}
}
}
@@ -0,0 +1,107 @@
package network.Messages.Enums;
import java.util.HashMap;
import java.util.Map;
/**
* Various device sources for a BoatLocation message.
*/
public enum BoatLocationDeviceEnum {
NOT_A_DEVICE(-1),
Unknown(0),
/**
* A yacht particpating in the race.
*/
RacingYacht(1),
CommitteeBoat(2),
/**
* A marker boat.
*/
Mark(3),
Pin(4),
ChaseBoat(5),
MedicalBoat(6),
MarshallBoat(7),
UmpireBoat(8),
UmpireSoftwareApplication(9),
PrincipalRaceOfficerApplication(10),
WeatherStation(11),
Helicopter(12),
DataProcessingApplication(13);
/**
* Value of the enum.
*/
private byte value;
/**
* Creates a BoatLocationDeviceEnum from a given primitive integer value, cast to a byte.
* @param value Integer, which is cast to byte, to construct from.
*/
private BoatLocationDeviceEnum(int value) {
this.value = (byte) value;
}
/**
* Returns the primitive value of the enum.
* @return Primitive value of the enum.
*/
public byte getValue() {
return value;
}
/**
* Stores a mapping between Byte values and BoatLocationDeviceEnum values.
*/
private static final Map<Byte, BoatLocationDeviceEnum> byteToDeviceMap = new HashMap<>();
/*
Static initialization block. Initializes the byteToDeviceMap.
*/
static {
for (BoatLocationDeviceEnum type : BoatLocationDeviceEnum.values()) {
BoatLocationDeviceEnum.byteToDeviceMap.put(type.value, type);
}
}
/**
* Returns the enumeration value which corresponds to a given byte value.
* @param deviceValue Byte value to convert to a BoatLocationDeviceEnum value.
* @return The BoatLocationDeviceEnum value which corresponds to the given byte value.
*/
public static BoatLocationDeviceEnum fromByte(byte deviceValue) {
//Gets the corresponding BoatLocationDeviceEnum from the map.
BoatLocationDeviceEnum type = BoatLocationDeviceEnum.byteToDeviceMap.get(deviceValue);
if (type == null) {
//If the byte value wasn't found, return the NOT_A_DEVICE BoatLocationDeviceEnum.
return BoatLocationDeviceEnum.NOT_A_DEVICE;
} else {
//Otherwise, return the BoatLocationDeviceEnum.
return type;
}
}
}
@@ -0,0 +1,113 @@
package network.Messages.Enums;
import java.util.HashMap;
import java.util.Map;
/**
* This enum encapsulates the different ways in which a server may respond to a client {@link network.Messages.RequestToJoin} message.
*/
public enum JoinAcceptanceEnum {
/**
* Client is allowed to join and spectate.
*/
JOIN_SUCCESSFUL_SPECTATOR(0),
/**
* Client is allowed to join and participate.
*/
JOIN_SUCCESSFUL_PARTICIPANT(1),
/**
* Client is allowed to join and play the tutorial.
*/
JOIN_SUCCESSFUL_TUTORIAL(2),
/**
* Client is allowed to join and participate as a ghost player.
*/
JOIN_SUCCESSFUL_GHOST(3),
/**
* Join Request was denied.
*/
JOIN_FAILURE(0x10),
/**
* The server is completely full, cannot participate or spectate.
*/
SERVER_FULL(0x11),
/**
* Used to indicate that a given byte value is invalid.
*/
NOT_AN_ACCEPTANCE_TYPE(-1);
/**
* Primitive value of the enum.
*/
private byte value;
/**
* Ctor. Creates a JoinAcceptanceEnum from a given primitive integer value, cast to a byte.
* @param value Integer, which is cast to byte, to construct from.
*/
private JoinAcceptanceEnum(int value) {
this.value = (byte) value;
}
/**
* Returns the primitive value of the enum.
* @return Primitive value of the enum.
*/
public byte getValue() {
return value;
}
/**
* Stores a mapping between Byte values and JoinAcceptanceEnum values.
*/
private static final Map<Byte, JoinAcceptanceEnum> byteToAcceptanceMap = new HashMap<>();
/*
Static initialization block. Initializes the byteToAcceptanceMap.
*/
static {
for (JoinAcceptanceEnum type : JoinAcceptanceEnum.values()) {
JoinAcceptanceEnum.byteToAcceptanceMap.put(type.value, type);
}
}
/**
* Returns the enumeration value which corresponds to a given byte value.
* @param joinAcceptanceEnum Byte value to convert to a JoinAcceptanceEnum value.
* @return The RequestToJoinEnum value which corresponds to the given byte value.
*/
public static JoinAcceptanceEnum fromByte(byte joinAcceptanceEnum) {
//Gets the corresponding MessageType from the map.
JoinAcceptanceEnum type = JoinAcceptanceEnum.byteToAcceptanceMap.get(joinAcceptanceEnum);
if (type == null) {
//If the byte value wasn't found, return the NOT_AN_ACCEPTANCE_TYPE JoinAcceptanceEnum.
return JoinAcceptanceEnum.NOT_AN_ACCEPTANCE_TYPE;
} else {
//Otherwise, return the JoinAcceptanceEnum.
return type;
}
}
}
@@ -0,0 +1,88 @@
package network.Messages.Enums;
import java.util.HashMap;
import java.util.Map;
/**
* Enumeration that encapsulates the various statuses a boat can have when rounding a mark.
*/
public enum MarkRoundingBoatStatusEnum {
UNKNOWN(0),
/**
* The boat is actively racing.
*/
RACING(1),
/**
* The boat has been disqualified.
*/
DSQ(2),
/**
* The boat has withdrawn from the race.
*/
WITHDRAWN(3),
NOT_A_STATUS(-1);
/**
* Primitive value of the enum.
*/
private byte value;
/**
* Ctor. Creates a {@link MarkRoundingBoatStatusEnum} from a given primitive integer value, cast to a byte.
* @param value Integer, which is cast to byte, to construct from.
*/
private MarkRoundingBoatStatusEnum(int value) {
this.value = (byte)value;
}
/**
* Returns the primitive value of the enum.
* @return Primitive value of the enum.
*/
public byte getValue() {
return value;
}
///Stores a mapping between Byte values and MarkRoundingBoatStatusEnum values.
private static final Map<Byte, MarkRoundingBoatStatusEnum> byteToStatusMap = new HashMap<>();
/*
Static initialization block. Initializes the byteToStatusMap.
*/
static {
for (MarkRoundingBoatStatusEnum type : MarkRoundingBoatStatusEnum.values()) {
byteToStatusMap.put(type.value, type);
}
}
/**
* Returns the enumeration value which corresponds to a given byte value.
* @param boatStatusByte Byte value to convert to a {@link MarkRoundingBoatStatusEnum} value.
* @return The {@link MarkRoundingBoatStatusEnum} value which corresponds to the given byte value.
*/
public static MarkRoundingBoatStatusEnum fromByte(byte boatStatusByte) {
//Gets the corresponding MarkRoundingBoatStatusEnum from the map.
MarkRoundingBoatStatusEnum type = byteToStatusMap.get(boatStatusByte);
if (type == null) {
//If the byte value wasn't found, return the NOT_A_STATUS MarkRoundingBoatStatusEnum.
return MarkRoundingBoatStatusEnum.NOT_A_STATUS;
}
else {
//Otherwise, return the MarkRoundingBoatStatusEnum.
return type;
}
}
}
@@ -0,0 +1,88 @@
package network.Messages.Enums;
import java.util.HashMap;
import java.util.Map;
/**
* Enumeration that encapsulates the various mark identities.
*/
public enum MarkRoundingIDEnum {
UNKNOWN(0),
ENTRY_LIMIT_LINE(100),
ENTRY_LINE(101),
START_LINE(102),
FINISH_LINE(103),
SPEED_TEST_START(104),
SPEED_TEST_FINISH(105),
CLEAR_START(106),
NOT_AN_ID(-1);
/**
* Primitive value of the enum.
*/
private byte value;
/**
* Ctor. Creates a {@link MarkRoundingIDEnum} from a given primitive integer value, cast to a byte.
* @param value Integer, which is cast to byte, to construct from.
*/
private MarkRoundingIDEnum(int value) {
this.value = (byte)value;
}
/**
* Returns the primitive value of the enum.
* @return Primitive value of the enum.
*/
public byte getValue() {
return value;
}
///Stores a mapping between Byte values and MarkRoundingIDEnum values.
private static final Map<Byte, MarkRoundingIDEnum> byteToIDMap = new HashMap<>();
/*
Static initialization block. Initializes the byteToIDMap.
*/
static {
for (MarkRoundingIDEnum type : MarkRoundingIDEnum.values()) {
byteToIDMap.put(type.value, type);
}
}
/**
* Returns the enumeration value which corresponds to a given byte value.
* @param sideByte Byte value to convert to a {@link MarkRoundingIDEnum} value.
* @return The {@link MarkRoundingIDEnum} value which corresponds to the given byte value.
*/
public static MarkRoundingIDEnum fromByte(byte sideByte) {
//Gets the corresponding MarkRoundingIDEnum from the map.
MarkRoundingIDEnum type = byteToIDMap.get(sideByte);
if (type == null) {
//If the byte value wasn't found, return the NOT_AN_ID MarkRoundingIDEnum.
return MarkRoundingIDEnum.NOT_AN_ID;
}
else {
//Otherwise, return the MarkRoundingIDEnum.
return type;
}
}
}
@@ -0,0 +1,82 @@
package network.Messages.Enums;
import java.util.HashMap;
import java.util.Map;
/**
* Enumeration that encapsulates the various sides around which a boat may pass a mark.
*/
public enum MarkRoundingSideEnum {
UNKNOWN(0),
/**
* Boat rounded around port side of mark.
*/
PORT(1),
/**
* Boat rounded around starboard side of mark.
*/
STARBOARD(2),
NOT_A_SIDE(-1);
/**
* Primitive value of the enum.
*/
private byte value;
/**
* Ctor. Creates a {@link MarkRoundingSideEnum} from a given primitive integer value, cast to a byte.
* @param value Integer, which is cast to byte, to construct from.
*/
private MarkRoundingSideEnum(int value) {
this.value = (byte)value;
}
/**
* Returns the primitive value of the enum.
* @return Primitive value of the enum.
*/
public byte getValue() {
return value;
}
///Stores a mapping between Byte values and MarkRoundingSideEnum values.
private static final Map<Byte, MarkRoundingSideEnum> byteToSideMap = new HashMap<>();
/*
Static initialization block. Initializes the byteToSideMap.
*/
static {
for (MarkRoundingSideEnum type : MarkRoundingSideEnum.values()) {
byteToSideMap.put(type.value, type);
}
}
/**
* Returns the enumeration value which corresponds to a given byte value.
* @param sideByte Byte value to convert to a {@link MarkRoundingSideEnum} value.
* @return The {@link MarkRoundingSideEnum} value which corresponds to the given byte value.
*/
public static MarkRoundingSideEnum fromByte(byte sideByte) {
//Gets the corresponding MarkRoundingSideEnum from the map.
MarkRoundingSideEnum type = byteToSideMap.get(sideByte);
if (type == null) {
//If the byte value wasn't found, return the NOT_A_SIDE MarkRoundingSideEnum.
return MarkRoundingSideEnum.NOT_A_SIDE;
}
else {
//Otherwise, return the MarkRoundingSideEnum.
return type;
}
}
}
@@ -0,0 +1,82 @@
package network.Messages.Enums;
import java.util.HashMap;
import java.util.Map;
/**
* Enumeration that encapsulates the various types of marks that may be passed.
*/
public enum MarkRoundingTypeEnum {
UNKNOWN(0),
/**
* The mark is a singular mark.
*/
MARK(1),
/**
* The mark is a gate (windward, leeward, start, finish, etc...).
*/
GATE(2),
NOT_A_TYPE(-1);
/**
* Primitive value of the enum.
*/
private byte value;
/**
* Ctor. Creates a {@link MarkRoundingTypeEnum} from a given primitive integer value, cast to a byte.
* @param value Integer, which is cast to byte, to construct from.
*/
private MarkRoundingTypeEnum(int value) {
this.value = (byte)value;
}
/**
* Returns the primitive value of the enum.
* @return Primitive value of the enum.
*/
public byte getValue() {
return value;
}
///Stores a mapping between Byte values and MarkRoundingTypeEnum values.
private static final Map<Byte, MarkRoundingTypeEnum> byteToTypeMap = new HashMap<>();
/*
Static initialization block. Initializes the byteToTypeMap.
*/
static {
for (MarkRoundingTypeEnum type : MarkRoundingTypeEnum.values()) {
byteToTypeMap.put(type.value, type);
}
}
/**
* Returns the enumeration value which corresponds to a given byte value.
* @param sideByte Byte value to convert to a {@link MarkRoundingTypeEnum} value.
* @return The {@link MarkRoundingTypeEnum} value which corresponds to the given byte value.
*/
public static MarkRoundingTypeEnum fromByte(byte sideByte) {
//Gets the corresponding MarkRoundingTypeEnum from the map.
MarkRoundingTypeEnum type = byteToTypeMap.get(sideByte);
if (type == null) {
//If the byte value wasn't found, return the NOT_A_TYPE MarkRoundingTypeEnum.
return MarkRoundingTypeEnum.NOT_A_TYPE;
}
else {
//Otherwise, return the MarkRoundingTypeEnum.
return type;
}
}
}
@@ -19,14 +19,37 @@ public enum MessageType {
MARKROUNDING(38),
COURSEWIND(44),
AVGWIND(47),
BOATACTION(100),
NOTAMESSAGE(0);
///Primitive value of the enum.
private byte value;
BOATACTION(100),
/**
* Ctor. Creates a MessageType enum from a given primitive integer value, cast to a byte.
* This is used for {@link network.Messages.RequestToJoin} messages.
*/
REQUEST_TO_JOIN(101),
/**
* This is used for {@link network.Messages.JoinAcceptance} messages.
*/
JOIN_ACCEPTANCE(102),
/**
* This is used for {@link network.Messages.AssignPlayerBoat} messages.
*/
ASSIGN_PLAYER_BOAT(121),
NOTAMESSAGE(0);
/**
* Primitive value of the enum.
*/
private byte value;
/**
* 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) {
@@ -0,0 +1,97 @@
package network.Messages.Enums;
import java.util.HashMap;
import java.util.Map;
/**
* Enumeration that encapsulates the various types race start status notifications. See AC35 streaming spec, 4.5.
*/
public enum RaceStartTypeEnum {
/**
* The race start time is being set.
*/
SET_RACE_START(1),
/**
* The race has been postponed.
*/
RACE_POSTPONED(2),
/**
* The race has been abandoned.
*/
RACE_ABANDONED(3),
/**
* The race has been terminated.
*/
RACE_TERMINATED(4),
/**
* Used to indicate that a given byte value is invalid.
*/
NOT_A_TYPE(-1);
/**
* Primitive value of the enum.
*/
private byte value;
/**
* Ctor. Creates a RaceStartTypeEnum from a given primitive integer value, cast to a byte.
* @param value Integer, which is cast to byte, to construct from.
*/
private RaceStartTypeEnum(int value) {
this.value = (byte) value;
}
/**
* Returns the primitive value of the enum.
* @return Primitive value of the enum.
*/
public byte getValue() {
return value;
}
/**
* Stores a mapping between Byte values and RaceStartTypeEnum values.
*/
private static final Map<Byte, RaceStartTypeEnum> byteToTypeMap = new HashMap<>();
/*
Static initialization block. Initializes the byteToTypeMap.
*/
static {
for (RaceStartTypeEnum type : RaceStartTypeEnum.values()) {
RaceStartTypeEnum.byteToTypeMap.put(type.value, type);
}
}
/**
* Returns the enumeration value which corresponds to a given byte value.
* @param startTypeEnum Byte value to convert to a RaceStartTypeEnum value.
* @return The RaceStartTypeEnum value which corresponds to the given byte value.
*/
public static RaceStartTypeEnum fromByte(byte startTypeEnum) {
//Gets the corresponding MessageType from the map.
RaceStartTypeEnum type = RaceStartTypeEnum.byteToTypeMap.get(startTypeEnum);
if (type == null) {
//If the byte value wasn't found, return the NOT_A_TYPE RaceStartTypeEnum.
return RaceStartTypeEnum.NOT_A_TYPE;
} else {
//Otherwise, return the RaceStartTypeEnum.
return type;
}
}
}
@@ -0,0 +1,102 @@
package network.Messages.Enums;
import java.util.HashMap;
import java.util.Map;
/**
* This enum encapsulates the different ways in which a client may wish to connect to a server.
*/
public enum RequestToJoinEnum {
/**
* Client wants to spectate.
*/
SPECTATOR(0),
/**
* Client wants to participate.
*/
PARTICIPANT(1),
/**
* Client wants to play the tutorial.
*/
CONTROL_TUTORIAL(2),
/**
* Client wants to particpate as a ghost.
*/
GHOST(3),
/**
* Used to indicate that a given byte value is invalid.
*/
NOT_A_REQUEST_TYPE(-1);
/**
* Primitive value of the enum.
*/
private int value;
/**
* Ctor. Creates a RequestToJoinEnum from a given int value.
* @param value Integer to construct from.
*/
private RequestToJoinEnum(int value) {
this.value = value;
}
/**
* Returns the primitive value of the enum.
* @return Primitive value of the enum.
*/
public int getValue() {
return value;
}
/**
* Stores a mapping between Integer values and RequestToJoinEnum values.
*/
private static final Map<Integer, RequestToJoinEnum> intToRequestMap = new HashMap<>();
/*
Static initialization block. Initializes the intToRequestMap.
*/
static {
for (RequestToJoinEnum type : RequestToJoinEnum.values()) {
RequestToJoinEnum.intToRequestMap.put(type.value, type);
}
}
/**
* Returns the enumeration value which corresponds to a given int value.
* @param requestToJoinEnum int value to convert to a RequestToJoinEnum value.
* @return The RequestToJoinEnum value which corresponds to the given int value.
*/
public static RequestToJoinEnum fromInt(int requestToJoinEnum) {
//Gets the corresponding MessageType from the map.
RequestToJoinEnum type = RequestToJoinEnum.intToRequestMap.get(requestToJoinEnum);
if (type == null) {
//If the int value wasn't found, return the NOT_A_REQUEST_TYPE RequestToJoinEnum.
return RequestToJoinEnum.NOT_A_REQUEST_TYPE;
} else {
//Otherwise, return the RequestToJoinEnum.
return type;
}
}
}
@@ -6,7 +6,7 @@ import network.Messages.Enums.MessageType;
/**
* Represents a Heartbeat message.
*/
public class Heartbeat extends AC35Data {
public class HeartBeat extends AC35Data {
/**
* Sequence number of the heartbeat.
@@ -17,7 +17,7 @@ public class Heartbeat extends AC35Data {
* Ctor.
* @param sequenceNumber Sequence number of the heartbeat.
*/
public Heartbeat(long sequenceNumber) {
public HeartBeat(long sequenceNumber) {
super(MessageType.HEARTBEAT);
this.sequenceNumber = sequenceNumber;
}
@@ -0,0 +1,54 @@
package network.Messages;
import network.Messages.Enums.JoinAcceptanceEnum;
import network.Messages.Enums.MessageType;
/**
* This is the message a server sends to a client to tell them their boat sourceID, and if they have actually managed to join the server.
*/
public class JoinAcceptance extends AC35Data {
/**
* The source ID of the boat assigned to the client.
* 0 indicates they haven't been assigned a boat.
*/
private int sourceID = 0;
/**
* The type of acceptance response this is.
*/
private JoinAcceptanceEnum acceptanceType;
/**
* Constructs a JoinAcceptance message of a given acceptance type.
* @param acceptanceType The type of join acceptance this is.
* @param sourceID The sourceID to assign to the client. 0 indicates no sourceID.
*/
public JoinAcceptance(JoinAcceptanceEnum acceptanceType, int sourceID){
super(MessageType.JOIN_ACCEPTANCE);
this.acceptanceType = acceptanceType;
this.sourceID = sourceID;
}
/**
* The type of acceptance response this is.
* @return The type of acceptance response.
*/
public JoinAcceptanceEnum getAcceptanceType() {
return acceptanceType;
}
/**
* Returns the source ID of the boat assigned to the client.
* @return The source ID of the boat assigned to the client.
*/
public int getSourceID() {
return sourceID;
}
}
@@ -2,9 +2,7 @@ package network.Messages;
import network.Messages.Enums.XMLMessageType;
import java.util.HashMap;
import java.util.Map;
import java.util.Observable;
import java.util.*;
/**
* This class contains a set of the latest messages received (e.g., the latest RaceStatus, the latest BoatLocation for each boat, etc...).
@@ -12,35 +10,11 @@ import java.util.Observable;
*/
public class LatestMessages extends Observable {
/**
* The latest RaceStatus message.
*/
private RaceStatus raceStatus;
/**
* A map of the last BoatStatus message received, for each boat.
* A list of messages containing a snapshot of the race.
*/
private final Map<Integer, BoatStatus> boatStatusMap = new HashMap<>();
/**
* A map of the last BoatLocation message received, for each boat.
*/
private final Map<Integer, BoatLocation> boatLocationMap = new HashMap<>();
/**
* A map of the last MarkRounding message received, for each boat.
*/
private final Map<Integer, MarkRounding> markRoundingMap = new HashMap<>();
/**
* The last AverageWind message received.
*/
private AverageWind averageWind;
/**
* The last CourseWinds message received.
*/
private CourseWinds courseWinds;
private List<AC35Data> snapshot = new ArrayList<>();
/**
@@ -68,140 +42,25 @@ public class LatestMessages extends Observable {
}
/**
* Gets the latest RaceStatus message received.
* @return The latest RaceStatus message received.
* Returns a copy of the race snapshot.
* @return Copy of the race snapshot.
*/
public RaceStatus getRaceStatus() {
return raceStatus;
}
/**
* Sets the latest RaceStatus message received.
* @param raceStatus The new RaceStatus message to store.
*/
public void setRaceStatus(RaceStatus raceStatus) {
this.raceStatus = raceStatus;
}
/**
* Returns the latest BoatStatus message received for a given boat.
* @param sourceID Source ID of the boat.
* @return The latest BoatStatus message for the specified boat.
*/
public BoatStatus getBoatStatus(int sourceID) {
return boatStatusMap.get(sourceID);
}
/**
* Inserts a BoatStatus message for a given boat.
* @param boatStatus The BoatStatus message to set.
*/
public void setBoatStatus(BoatStatus boatStatus) {
boatStatusMap.put(boatStatus.getSourceID(), boatStatus);
}
/**
* Returns the latest BoatLocation message received for a given boat.
* @param sourceID Source ID of the boat.
* @return The latest BoatLocation message for the specified boat.
*/
public BoatLocation getBoatLocation(int sourceID) {
return boatLocationMap.get(sourceID);
}
/**
* Inserts a BoatLocation message for a given boat.
* @param boatLocation The BoatLocation message to set.
*/
public void setBoatLocation(BoatLocation boatLocation) {
//TODO should compare the sequence number of the new boatLocation with the existing boatLocation for this boat (if it exists), and use the newer one.
boatLocationMap.put(boatLocation.getSourceID(), boatLocation);
}
/**
* Returns the latest MarkRounding message received for a given boat.
* @param sourceID Source ID of the boat.
* @return The latest MarkRounding message for the specified boat.
*/
public MarkRounding getMarkRounding(int sourceID) {
return markRoundingMap.get(sourceID);
}
/**
* Inserts a MarkRounding message for a given boat.
* @param markRounding The MarkRounding message to set.
*/
public void setMarkRounding(MarkRounding markRounding) {
//TODO should compare the sequence number of the new markRounding with the existing boatLocation for this boat (if it exists), and use the newer one.
markRoundingMap.put(markRounding.getSourceID(), markRounding);
}
/**
* Gets the latest AverageWind message received.
* @return The latest AverageWind message received.
*/
public AverageWind getAverageWind() {
return averageWind;
}
/**
* Sets the latest AverageWind message received.
* @param averageWind The new AverageWind message to store.
*/
public void setAverageWind(AverageWind averageWind) {
this.averageWind = averageWind;
public List<AC35Data> getSnapshot() {
return new ArrayList<>(snapshot);
}
/**
* Gets the latest CourseWinds message received.
* @return The latest CourseWinds message received.
* Sets the snapshot of the race.
* @param snapshot New snapshot of race.
*/
public CourseWinds getCourseWinds() {
return courseWinds;
}
/**
* Sets the latest CourseWinds message received.
* @param courseWinds The new CourseWinds message to store.
*/
public void setCourseWinds(CourseWinds courseWinds) {
this.courseWinds = courseWinds;
public void setSnapshot(List<AC35Data> snapshot) {
this.snapshot = snapshot;
}
/**
* Returns the map of boat sourceIDs to BoatLocation messages.
* @return Map between boat sourceID and BoatLocation.
*/
public Map<Integer, BoatLocation> getBoatLocationMap() {
return boatLocationMap;
}
/**
* Returns the map of boat sourceIDs to BoatStatus messages.
* @return Map between boat sourceID and BoatStatus.
*/
public Map<Integer, BoatStatus> getBoatStatusMap() {
return boatStatusMap;
}
/**
* Returns the map of boat sourceIDs to MarkRounding messages.
* @return Map between boat sourceID and MarkRounding.
*/
public Map<Integer, MarkRounding> getMarkRoundingMap() {
return markRoundingMap;
}
@@ -1,47 +1,94 @@
package network.Messages;
import network.Messages.Enums.MarkRoundingBoatStatusEnum;
import network.Messages.Enums.MarkRoundingSideEnum;
import network.Messages.Enums.MarkRoundingTypeEnum;
import network.Messages.Enums.MessageType;
/**
* Created by fwy13 on 25/04/17.
* Represents a MarkRound message (see AC35 spec, 4.10).
*/
public class MarkRounding extends AC35Data {
private int msgVerNum;
/**
* The current messageVersionNumber according to the API spec.
*/
public static final byte currentMessageVersionNumber = 1;
/**
* Version number of the message.
*/
private byte messageVersionNumber;
/**
* The time at which the mark was rounding. Milliseconds since unix epoch.
*/
private long time;
/**
* The ack number of the message.
*/
private int ackNum;
/**
* The raceID this message relates to.
*/
private int raceID;
/**
* The sourceID of the boat this message relates to.
*/
private int sourceID;
private int boatStatus;
private int roundingSide;
private int markType;
private int markID;
public static int BoatStatusUnknown = 0;
public static int BoatStatusRacing = 1;
public static int BoatStatusDSQ = 2;
public static int BoatStatusWithdrawn = 3;
/**
* The status of the boat.
*/
private MarkRoundingBoatStatusEnum boatStatus;
public static int RoundingSideUnknown = 0;
public static int RoundingSidePort = 1;
public static int RoundingSideStarboard = 2;
/**
* The side around which the boat rounded.
*/
private MarkRoundingSideEnum roundingSide;
public static int MarkTypeUnknown = 0;
public static int MarkTypeRoundingMark = 1;
public static int MarkTypeGate = 2;
/**
* The type of mark that was rounded.
*/
private MarkRoundingTypeEnum markType;
public static int MarkIDEntryLimitLine = 100;
public static int MarkIDEntryLine = 101;
public static int MarkIDRaceStartStartline = 102;
public static int MarkIDRaceFinishline = 103;
public static int MarkIDSpeedTestStart = 104;
public static int MarkIDSpeedTestFinish = 105;
public static int MarkIDClearStart = 106;
/**
* The ID of the mark. This is not a source ID.
*/
private byte markID;
/**
* Creates a MarkRounding message with the given parameters.
* @param messageVersionNumber The version number of the message.
* @param time The time at which the message was created.
* @param ackNum The ack number of the message.
* @param raceID The raceID this message relates to.
* @param sourceID The sourceID of the boat this message relates to.
* @param boatStatus The status of the boat as it rounded the mark.
* @param roundingSide The side around which the boat rounded.
* @param markType The type of mark that was rounded.
* @param markID The ID number of the mark. Not a sourceID. See {@link network.Messages.Enums.MarkRoundingIDEnum}.
*/
public MarkRounding(
byte messageVersionNumber,
long time,
int ackNum,
int raceID,
int sourceID,
MarkRoundingBoatStatusEnum boatStatus,
MarkRoundingSideEnum roundingSide,
MarkRoundingTypeEnum markType,
byte markID ) {
public MarkRounding(int msgVerNum, long time, int ackNum, int raceID, int sourceID, int boatStatus, int roundingSide, int markType, int markID){
super(MessageType.MARKROUNDING);
this.msgVerNum = msgVerNum;
this.messageVersionNumber = messageVersionNumber;
this.time = time;
this.ackNum = ackNum;
this.raceID = raceID;
@@ -52,6 +99,40 @@ public class MarkRounding extends AC35Data {
this.markID = markID;
}
/**
* Returns the version number of this message.
* @return Version number of this message.
*/
public byte getMessageVersionNumber() {
return messageVersionNumber;
}
/**
* Returns the timestamp for this message.
* @return Timestamp for this message.
*/
public long getTime() {
return time;
}
/**
* Returns the ack number of this message.
* @return Ack number of this message.
*/
public int getAckNum() {
return ackNum;
}
/**
* Returns the raceID this message relates to.
* @return RaceID this message relates to.
*/
public int getRaceID() {
return raceID;
}
/**
* Returns the boat (source) ID for this message.
* @return Boat ID for this message.
@@ -61,10 +142,34 @@ public class MarkRounding extends AC35Data {
}
/**
* Returns the timestamp for this message.
* @return Timestamp for this message.
* Returns the status of the boat as it rounded the mark.
* @return Status of boat as it rounded mark.
*/
public long getTime() {
return time;
public MarkRoundingBoatStatusEnum getBoatStatus() {
return boatStatus;
}
/**
* Returns the side to which the boat rounded the mark.
* @return Side to which boat rounded mark.
*/
public MarkRoundingSideEnum getRoundingSide() {
return roundingSide;
}
/**
* Returns the type of mark that was rounded.
* @return The type of mark that was rounded.
*/
public MarkRoundingTypeEnum getMarkType() {
return markType;
}
/**
* Returns ID number of the mark. This is not a source ID. See {@link network.Messages.Enums.MarkRoundingIDEnum}.
* @return ID number of the mark.
*/
public byte getMarkID() {
return markID;
}
}
@@ -0,0 +1,41 @@
package network.Messages;
import java.util.ArrayList;
import java.util.List;
/**
* Represents a snapshot of the race's state.
* Contains a list of {@link AC35Data} messages.
* Send a copy of each message to a connected client.
*/
public class RaceSnapshot {
/**
* The contents of the snapshot.
*/
private List<AC35Data> snapshot;
/**
* Constructs a snapshot using a given list of messages.
* @param snapshot Messages to use as snapshot.
*/
public RaceSnapshot(List<AC35Data> snapshot) {
this.snapshot = snapshot;
}
/**
* Gets the contents of the snapshot.
* This is a shallow copy.
* @return Contents of the snapshot.
*/
public List<AC35Data> getSnapshot() {
List<AC35Data> copy = new ArrayList<>(snapshot);
return copy;
}
}
@@ -2,20 +2,63 @@ package network.Messages;
import network.Messages.Enums.MessageType;
import network.Messages.Enums.RaceStartTypeEnum;
/**
* Created by fwy13 on 25/04/17.
* Represents a RaceStartStatus message from the API, section 4.5.
*/
public class RaceStartStatus extends AC35Data {
private long timestamp;
private int ackNum;
private long raceStartTime;
private int raceID;
private int notificationType;
/**
* The current version number of this message type.
*/
public static final byte currentMessageVersionNumber = 1;
public RaceStartStatus(long timestamp, int ackNum, long raceStartTime, int raceID, int notificationType){
/**
* The version number of this message.
*/
private byte messageVersionNumber;
/**
* The time at which this message was created. Milliseconds since unix epoch.
*/
private long timestamp;
/**
* Sequence number of message.
*/
private int ackNum;
/**
* The time the race is expected to start at. Milliseconds since unix epoch.
*/
private long raceStartTime;
/**
* The ID of the race this message relates to.
*/
private int raceID;
/**
* The type of notification this is.
*/
private RaceStartTypeEnum notificationType;
/**
* Constructs a RaceStartStatus message with the given parameters.
* @param messageVersionNumber Version number of the message.
* @param timestamp The timestamp at which this message was generated.
* @param ackNum The sequence number of this message.
* @param raceStartTime The expected race start time.
* @param raceID The ID of the race this message relates to.
* @param notificationType The type of notification this is.
*/
public RaceStartStatus(byte messageVersionNumber, long timestamp, int ackNum, long raceStartTime, int raceID, RaceStartTypeEnum notificationType) {
super(MessageType.RACESTARTSTATUS);
this.messageVersionNumber = messageVersionNumber;
this.timestamp = timestamp;
this.ackNum = ackNum;
this.raceStartTime = raceStartTime;
@@ -23,4 +66,52 @@ public class RaceStartStatus extends AC35Data {
this.notificationType = notificationType;
}
/**
* Returns the version number of this message.
* @return Version number of this message.
*/
public byte getMessageVersionNumber() {
return messageVersionNumber;
}
/**
* Return the time at which this message was generated. Milliseconds since unix epoch.
* @return Time at which this message was generated.
*/
public long getTimestamp() {
return timestamp;
}
/**
* Returns the sequence number of this message.
* @return Sequence number of this message.
*/
public int getAckNum() {
return ackNum;
}
/**
* Returns the expected race start time. Milliseconds since unix epoch.
* @return Expected race start time.
*/
public long getRaceStartTime() {
return raceStartTime;
}
/**
* Returns the race ID this message relates to.
* @return Race ID this message relates to.
*/
public int getRaceID() {
return raceID;
}
/**
* Returns the type of start status notification this message is.
* @return The type of notification this is.
*/
public RaceStartTypeEnum getNotificationType() {
return notificationType;
}
}
@@ -2,27 +2,92 @@ 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.Bearing;
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;
private int windDirection;
private int windSpeed;
private int raceType;
/**
* The wind direction of the course.
*/
private Bearing windDirection;
/**
* The wind speed of the course.
* Knots.
*/
private double windSpeed;
/**
* The type of race this is.
*/
private RaceTypeEnum raceType;
/**
* A list of boat statuses.
* One for each boat.
*/
private List<BoatStatus> boatStatuses;
public RaceStatus(long currentTime, int raceID, byte raceStatus, long expectedStartTime, int windDirection, int windSpeed, int raceType, List<BoatStatus> 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, in knots.
* @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, Bearing windDirection, double windSpeed, RaceTypeEnum raceType, List<BoatStatus> boatStatuses) {
super(MessageType.RACESTATUS);
this.messageVersionNumber = messageVersionNumber;
this.currentTime = currentTime;
this.raceID = raceID;
this.raceStatus = raceStatus;
@@ -30,114 +95,134 @@ 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;
}
///Getters.
/**
* Returns the version number of this message.
* @return The version number of the message.
*/
public byte getMessageVersionNumber() {
return messageVersionNumber;
}
/**
* 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;
}
public int getWindDirection()
/**
* Returns the current direction of the wind in the race.
* @return Current wind direction.
*/
public Bearing getWindDirection()
{
return windDirection;
}
/**
* Returns the wind speed for this race status, in millimeters per second.
* @return Wind speed in millimeters per second.
*/
public int getWindSpeed()
{
return windSpeed;
}
public int getRaceType()
{
return raceType;
}
public List<BoatStatus> getBoatStatuses()
{
return boatStatuses;
}
public boolean isNotActive() {
return raceStatus == 0;
}
public boolean isWarning() {
return raceStatus == 1;
}
public boolean isPreparatory() {
return raceStatus == 2;
}
public boolean isStarted() {
return raceStatus == 3;
}
public boolean isFinished() {
return raceStatus == 4;
}
public boolean isRetired() {
return raceStatus == 5;
}
public boolean isAbandoned() {
return raceStatus == 6;
}
public boolean isPostponed() {
return raceStatus == 7;
}
public boolean isTerminated() {
return raceStatus == 8;
}
public boolean isStartTimeSet() {
return raceStatus != 9;
}
public boolean isPrestart() {
return raceStatus == 10;
}
public double getScaledWindDirection() {
return AC35UnitConverter.convertHeading(windDirection);
}
/**
* Returns the wind speed for this race status, in knots.
* @return Wind speed in knots.
*/
public double getWindSpeedKnots() {
return (windSpeed / Constants.KnotsToMMPerSecond);
public double getWindSpeed()
{
return windSpeed;
}
/**
* 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<BoatStatus> getBoatStatuses()
{
return boatStatuses;
}
public boolean isNotActive() {
return raceStatus == RaceStatusEnum.NOT_ACTIVE;
}
public boolean isWarning() {
return raceStatus == RaceStatusEnum.WARNING;
}
public boolean isPreparatory() {
return raceStatus == RaceStatusEnum.PREPARATORY;
}
public boolean isStarted() {
return raceStatus == RaceStatusEnum.STARTED;
}
public boolean isFinished() {
return raceStatus == RaceStatusEnum.FINISHED;
}
public boolean isRetired() {
return raceStatus == RaceStatusEnum.RETIRED;
}
public boolean isAbandoned() {
return raceStatus == RaceStatusEnum.ABANDONED;
}
public boolean isPostponed() {
return raceStatus == RaceStatusEnum.POSTPONED;
}
public boolean isTerminated() {
return raceStatus == RaceStatusEnum.TERMINATED;
}
public boolean isStartTimeSet() {
return raceStatus != RaceStatusEnum.RACE_START_TIME_NOT_SET;
}
public boolean isPrestart() {
return raceStatus == RaceStatusEnum.PRESTART;
}
}
@@ -0,0 +1,36 @@
package network.Messages;
import network.Messages.Enums.MessageType;
import network.Messages.Enums.RequestToJoinEnum;
/**
* This is the message a client sends to a server to request to join/view a race.
*/
public class RequestToJoin extends AC35Data {
/**
* The type of join request this is.
*/
private RequestToJoinEnum requestType;
/**
* Constructs a RequestToJoin message of a given request type.
* @param requestType The type of join request this is.
*/
public RequestToJoin(RequestToJoinEnum requestType){
super(MessageType.REQUEST_TO_JOIN);
this.requestType = requestType;
}
/**
* The type of join request this is.
* @return The type of join request.
*/
public RequestToJoinEnum getRequestType() {
return requestType;
}
}
@@ -0,0 +1,152 @@
package network.StreamRelated;
import network.BinaryMessageDecoder;
import network.Exceptions.InvalidMessageException;
import network.Messages.AC35Data;
import shared.model.RunnableWithFramePeriod;
import java.io.*;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.concurrent.BlockingQueue;
import java.util.logging.Level;
import java.util.logging.Logger;
import static network.Utils.ByteConverter.bytesToShort;
/**
* This class is responsible for converting data from an input stream into a queue of {@link AC35Data} messages.
*/
public class MessageDeserialiser implements RunnableWithFramePeriod {
/**
* The stream we're reading from.
*/
private DataInputStream inputStream;
/**
* The messages we've read.
*/
private BlockingQueue<AC35Data> messagesRead;
/**
* Determines whether or not this runnable is currently running.
*/
private boolean isRunning;
/**
* Constructs a new MessageSerialiser to write a queue of messages to a given stream.
* @param inputStream The stream to write to.
* @param messagesRead The messages to send.
*/
public MessageDeserialiser(InputStream inputStream, BlockingQueue<AC35Data> messagesRead) {
this.inputStream = new DataInputStream(inputStream);
this.messagesRead = messagesRead;
}
/**
* Returns the queue of messages read from the socket.
* @return Queue of messages read from socket.
*/
public BlockingQueue<AC35Data> getMessagesRead() {
return messagesRead;
}
/**
* Reads and returns the next message as an array of bytes from the input stream. Use getNextMessage() to get the actual message object instead.
* @return Encoded binary message bytes.
* @throws IOException Thrown when an error occurs while reading from the input stream.
*/
private byte[] getNextMessageBytes() throws IOException {
inputStream.mark(0);
short CRCLength = 4;
short headerLength = 15;
//Read the header of the next message.
byte[] headerBytes = new byte[headerLength];
inputStream.readFully(headerBytes);
//Read the message body length.
byte[] messageBodyLengthBytes = Arrays.copyOfRange(headerBytes, headerLength - 2, headerLength);
short messageBodyLength = bytesToShort(messageBodyLengthBytes);
//Read the message body.
byte[] messageBodyBytes = new byte[messageBodyLength];
inputStream.readFully(messageBodyBytes);
//Read the message CRC.
byte[] messageCRCBytes = new byte[CRCLength];
inputStream.readFully(messageCRCBytes);
//Put the head + body + crc into one large array.
ByteBuffer messageBytes = ByteBuffer.allocate(headerBytes.length + messageBodyBytes.length + messageCRCBytes.length);
messageBytes.put(headerBytes);
messageBytes.put(messageBodyBytes);
messageBytes.put(messageCRCBytes);
return messageBytes.array();
}
/**
* Reads and returns the next message object from the input stream.
* @return The message object.
* @throws IOException Thrown when an error occurs while reading from the input stream.
* @throws InvalidMessageException Thrown when the message is invalid in some way.
*/
private AC35Data getNextMessage() throws IOException, InvalidMessageException
{
//Get the next message from the socket as a block of bytes.
byte[] messageBytes = this.getNextMessageBytes();
//Decode the binary message into an appropriate message object.
BinaryMessageDecoder decoder = new BinaryMessageDecoder(messageBytes);
return decoder.decode();
}
/**
* Determines whether or not this runnable is running.
* @return True means that it is still running, false means that it has stopped.
*/
public boolean isRunning() {
return isRunning;
}
@Override
public void run() {
isRunning = true;
while (!Thread.interrupted()) {
//Reads the next message.
try {
AC35Data message = this.getNextMessage();
messagesRead.add(message);
}
catch (InvalidMessageException e) {
Logger.getGlobal().log(Level.WARNING, "Unable to read message on thread: " + Thread.currentThread() + ".", e);
} catch (IOException e) {
Logger.getGlobal().log(Level.SEVERE, "Unable to read inputStream: " + inputStream + " on thread: " + Thread.currentThread() + ".", e);
isRunning = false;
return;
}
}
}
}
@@ -0,0 +1,125 @@
package network.StreamRelated;
import network.Exceptions.InvalidMessageException;
import network.MessageEncoders.RaceVisionByteEncoder;
import network.Messages.AC35Data;
import shared.model.RunnableWithFramePeriod;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* This class is responsible for writing a queue of {@link network.Messages.AC35Data} messages to an output stream.
*/
public class MessageSerialiser implements RunnableWithFramePeriod {
/**
* The stream we're writing to.
*/
private DataOutputStream outputStream;
/**
* The messages we're writing to the stream.
*/
private BlockingQueue<AC35Data> messagesToSend;
/**
* Ack numbers used in messages.
*/
private int ackNumber = 1;
/**
* Determines whether or not this runnable is currently running.
*/
private boolean isRunning;
/**
* Constructs a new MessageSerialiser to write a queue of messages to a given stream.
* @param outputStream The stream to write to.
* @param messagesToSend The messages to send.
*/
public MessageSerialiser(OutputStream outputStream, BlockingQueue<AC35Data> messagesToSend) {
this.outputStream = new DataOutputStream(outputStream);
this.messagesToSend = messagesToSend;
}
/**
* Returns the queue of messages to write to the socket.
* @return Queue of messages to write to the socket.
*/
public BlockingQueue<AC35Data> getMessagesToSend() {
return messagesToSend;
}
/**
* Increments the ackNumber value, and returns it.
* @return Incremented ackNumber.
*/
private int getNextAckNumber(){
this.ackNumber++;
return this.ackNumber;
}
/**
* Determines whether or not this runnable is running.
* @return True means that it is still running, false means that it has stopped.
*/
public boolean isRunning() {
return isRunning;
}
@Override
public void run() {
long previousFrameTime = System.currentTimeMillis();
isRunning = true;
while (!Thread.interrupted()) {
long currentFrameTime = System.currentTimeMillis();
waitForFramePeriod(previousFrameTime, currentFrameTime, 16);
previousFrameTime = currentFrameTime;
//Send the messages.
List<AC35Data> messages = new ArrayList<>();
messagesToSend.drainTo(messages);
for (AC35Data message : messages) {
try {
byte[] messageBytes = RaceVisionByteEncoder.encodeBinaryMessage(message, getNextAckNumber());
outputStream.write(messageBytes);
} catch (InvalidMessageException e) {
Logger.getGlobal().log(Level.WARNING, "Could not encode message: " + message, e);
} catch (IOException e) {
Logger.getGlobal().log(Level.SEVERE, "Could not write message to outputStream: " + outputStream + " on thread: " + Thread.currentThread(), e);
isRunning = false;
return;
}
}
}
}
}
@@ -1,43 +1,107 @@
package network.Utils;
import shared.model.Constants;
/**
* Created by fwy13 on 28/04/17.
* Contains various unit conversion for encoding/decoding messages.
* Our program uses the "unpacked" units, and the over-the-wire format uses "packed" units (e.g., degrees stored as ints).
*/
public class AC35UnitConverter {
public static double convertGPS(int value){
//converts latitude or longitue to angle
/**
* Converts a packed GPSCoordinate (latitude or longitude) into the unpacked unit.
* @param value Packed lat/long value.
* @return Unpacked lat/long angle, in degrees.
*/
public static double unpackGPS(int value) {
return (double) value * 180.0 / 2147483648.0;//2^31 = 2147483648
}
public static int convertGPSToInt(double value){
//converts latitude or longitue to angle
return (int) (value * 2147483648.0/180.0);//2^31 = 2147483648
}
public static double convertHeading(long value){
return (double) value * 360.0/65536.0;//2^15
}
public static double convertHeading(int value){
return (double) value * 360.0/65536.0;//2^15
/**
* Converts a latitude or longitude angle into a packed unit.
* @param value The lat/long angle, in degrees, to convert.
* @return The packed value.
*/
public static int packGPS(double value) {
return (int) (value * 2147483648.0 / 180.0);//2^31 = 2147483648
}
public static double convertHeading(double value){
return value * 360.0/65536.0;//2^15
/**
* Unpacks a heading from an int to an angle in degrees (this is a bearing).
* @param value The packed value to unpack.
* @return The unpacked value in degrees.
*/
public static double unpackHeading(int value) {
return (value * 360.0 / 65536.0);//2^15
}
public static int encodeHeading(int value){
/**
* Packs a heading (this is a bearing), in degrees, to a packed int value.
* @param value The heading in degrees.
* @return The packed value.
*/
public static int packHeading(double value) {
return (int) (value / 360.0 * 65536.0);//2^15
}
public static int encodeHeading(double value){
return (int) (value / 360.0 * 65536.0);//2^15
/**
* Unpacks a true wind angle from a short to an angle in degrees (this is an azimuth).
* @param value The packed value to unpack.
* @return The unpacked value in degrees.
*/
public static double unpackTrueWindAngle(short value) {
return (value * 180.0 / 32768.0);//-2^15 to 2^15
}
public static double convertTrueWindAngle(long value){
return (double) value * 180.0/32768.0;//-2^15 to 2^15
/**
* Packs a true wind angle (this is an azimuth) from an angle in degrees to a packed short value.
* @param value The unpacked value in degrees.
* @return The packed value.
*/
public static short packTrueWindAngle(double value) {
return (short) (value / 180.0 * 32768.0);//-2^15 to 2^15
}
/**
* Unpacks a speed, in millimeters per second, to a double, in knots.
* @param millimetersPerSec Speed in millimeters per second.
* @return Speed in knots.
*/
public static double unpackMMperSecToKnots(int millimetersPerSec) {
return (millimetersPerSec / Constants.KnotsToMMPerSecond);
}
/**
* Packs a speed, in knots, into an int, in millimeters per second.
* @param speedKnots Speed in knots.
* @return Speed in millimeters per second.
*/
public static int packKnotsToMMperSec(double speedKnots) {
return (int) (speedKnots * Constants.KnotsToMMPerSecond);
}
/**
* Packs an average wind period, in milliseconds, into an int, in tenths of a second.
* @param periodMilliseconds Period in milliseconds.
* @return Period in tenths of a second.
*/
public static int packAverageWindPeriod(long periodMilliseconds) {
return (int) (periodMilliseconds / 100);
}
/**
* Unpacks an average wind period, in tenths of a second, into an long, in milliseconds.
* @param periodTenthsOfSecond Period in tenths of a second.
* @return Period in milliseconds
*/
public static long unpackAverageWindPeriod(int periodTenthsOfSecond) {
return (periodTenthsOfSecond * 100);
}
}