Merge branch 'issue_28_36_decoders' into 'master'

Issue 28 & 36 - encoders and decoders refactor

This resolves #28 #36.
This was on the story_61 branch, but I figured it may be helpful to push this into master/other branches before then.

The main changes:
- Moved encoding functions from RaceVisionByteEncoder into their own encoder classes.
- Added a EncoderFactory and DecoderFactory, which return correct en/decoder based on MessageType.
- All encoders implement MessageEncoder interface, and all decoder implement MessageDecoder interface.

Extra changes:
- Added names to most threads that are created.
- Removed the nested while loop from MockOutput, and moved the XML stuff before the main loop (functionally it's the same).

See merge request !23
main
Fraser Cope 8 years ago
commit cdb6219cce

@ -52,10 +52,10 @@ public class ConnectionAcceptor implements Runnable {
*/
public ConnectionAcceptor(LatestMessages latestMessages) throws IOException {
this.latestMessages =latestMessages;
this.latestMessages = latestMessages;
this.serverSocket = new ServerSocket(serverPort);
CheckClientConnection checkClientConnection = new CheckClientConnection(mockOutputList);
new Thread(checkClientConnection).start();
new Thread(checkClientConnection, "ConnectionAcceptor()->CheckClientConnection thread").start();
}
public String getAddress() throws UnknownHostException {
@ -76,19 +76,30 @@ public class ConnectionAcceptor implements Runnable {
*/
@Override
public void run() {
while(true){//should be connections not filled up
while(mockOutputList.remainingCapacity() > 0) {
try {
System.out.println("Waiting for a connection...");//TEMP DEBUG REMOVE
Socket mockSocket = serverSocket.accept();
//TODO at this point we need to assign the connection a boat source ID, if they requested to participate.
DataOutputStream outToVisualiser = new DataOutputStream(mockSocket.getOutputStream());
MockOutput mockOutput = new MockOutput(latestMessages, outToVisualiser);
this.controllerServer = new ControllerServer(mockSocket, rl);
new Thread(mockOutput).start();
new Thread(controllerServer).start();
ControllerServer controllerServer = new ControllerServer(mockSocket, this.rl); //TODO probably pass assigned boat source ID into ControllerServer.
new Thread(mockOutput, "ConnectionAcceptor.run()->MockOutput thread" + mockOutput).start();
new Thread(controllerServer, "ConnectionAcceptor.run()->ControllerServer thread" + controllerServer).start();
mockOutputList.add(mockOutput);
System.out.println(String.format("%d number of Visualisers Connected.", mockOutputList.size()));
System.out.println(String.format("%d number of Visualisers Connected.", mockOutputList.size()));//TEMP
} catch (IOException e) {
e.printStackTrace();
e.printStackTrace();//TODO handle this properly
}
}
@ -117,7 +128,7 @@ public class ConnectionAcceptor implements Runnable {
double timeSinceLastHeartBeat = System.currentTimeMillis();
while(true) {
//System.out.println(mocks.size());//used to see current amount of visualisers connected.
ArrayBlockingQueue<MockOutput> m = new ArrayBlockingQueue(16, true, mocks);
ArrayBlockingQueue<MockOutput> m = new ArrayBlockingQueue<>(16, true, mocks);
for (MockOutput mo : m) {
try {
mo.sendHeartBeat();

@ -35,7 +35,7 @@ public class Event {
private Polars boatPolars;
private ConnectionAcceptor mockOutput;
private ConnectionAcceptor connectionAcceptor;
private LatestMessages latestMessages;
/**
@ -51,7 +51,7 @@ public class Event {
this.boatPolars = PolarParser.parse("mock/polars/acc_polars.csv");
this.latestMessages = new LatestMessages();
this.mockOutput = new ConnectionAcceptor(latestMessages);
this.connectionAcceptor = new ConnectionAcceptor(latestMessages);
}
catch (IOException e) {
e.printStackTrace();
@ -67,11 +67,11 @@ public class Event {
}
public String getAddress() throws UnknownHostException {
return mockOutput.getAddress();
return connectionAcceptor.getAddress();
}
public int getPort() {
return mockOutput.getServerPort();
return connectionAcceptor.getServerPort();
}
/**
@ -82,7 +82,7 @@ public class Event {
* @throws InvalidRegattaDataException Thrown if the regatta xml file cannot be parsed.
*/
public void start() throws InvalidRaceDataException, XMLReaderException, InvalidBoatDataException, InvalidRegattaDataException {
new Thread(mockOutput).start();
new Thread(connectionAcceptor, "Event.Start()->ConnectionAcceptor thread").start();
sendXMLs();
@ -94,9 +94,9 @@ public class Event {
//Create and start race.
RaceLogic newRace = new RaceLogic(new MockRace(boatDataSource, raceDataSource, regattaDataSource, this.latestMessages, this.boatPolars, Constants.RaceTimeScale), this.latestMessages);
mockOutput.setRace(newRace);
connectionAcceptor.setRace(newRace);
new Thread(newRace, "Event.Start()->RaceLogic thread").start();
new Thread(newRace).start();
}
/**
@ -104,11 +104,11 @@ public class Event {
*/
private void sendXMLs() {
mockOutput.setRegattaXml(regattaXML);
connectionAcceptor.setRegattaXml(regattaXML);
mockOutput.setRaceXml(raceXML);
connectionAcceptor.setRaceXml(raceXML);
mockOutput.setBoatsXml(boatXML);
connectionAcceptor.setBoatsXml(boatXML);
}
/**

@ -3,16 +3,16 @@ package mock.app;
import network.BinaryMessageEncoder;
import network.Exceptions.InvalidMessageException;
import network.MessageEncoders.RaceVisionByteEncoder;
import network.Messages.*;
import network.Messages.Enums.MessageType;
import network.Messages.Enums.XMLMessageType;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* TCP server to send race information to connected clients.
@ -54,8 +54,6 @@ public class MockOutput implements Runnable
private int heartbeatSequenceNum = 1;
private boolean stop = false; //whether or not hte thread keeps running
/**
* Ctor.
* @param latestMessages Latests Messages that the Mock is to send out
@ -98,24 +96,25 @@ public class MockOutput implements Runnable
* Generates the next heartbeat message and returns it. Increments the heartbeat sequence number.
* @return The next heartbeat message.
*/
private Heartbeat createHeartbeatMessage() {
private HeartBeat createHeartbeatMessage() {
//Create the heartbeat message.
Heartbeat heartbeat = new Heartbeat(this.heartbeatSequenceNum);
HeartBeat heartBeat = new HeartBeat(this.heartbeatSequenceNum);
heartbeatSequenceNum++;
return heartbeat;
return heartBeat;
}
/**
* Serializes a heartbeat message into a packet to be sent, and returns the byte array.
* @param heartbeat The heartbeat message to serialize.
* @param heartBeat The heartbeat message to serialize.
* @return Byte array containing the next heartbeat message.
* @throws InvalidMessageException Thrown if the message cannot be encoded.
*/
private byte[] parseHeartbeat(Heartbeat heartbeat) {
private byte[] parseHeartbeat(HeartBeat heartBeat) throws InvalidMessageException {
//Serializes the heartbeat message.
byte[] heartbeatMessage = RaceVisionByteEncoder.heartBeat(heartbeat);
byte[] heartbeatMessage = RaceVisionByteEncoder.encode(heartBeat);
//Places the serialized message in a packet.
BinaryMessageEncoder binaryMessageEncoder = new BinaryMessageEncoder(
@ -133,11 +132,12 @@ public class MockOutput implements Runnable
* Encodes/serialises a XMLMessage message, and returns it.
* @param xmlMessage The XMLMessage message to serialise.
* @return The XMLMessage message in a serialised form.
* @throws InvalidMessageException Thrown if the message cannot be encoded.
*/
private synchronized byte[] parseXMLMessage(XMLMessage xmlMessage) {
private synchronized byte[] parseXMLMessage(XMLMessage xmlMessage) throws InvalidMessageException {
//Serialize the xml message.
byte[] encodedXML = RaceVisionByteEncoder.xmlMessage(xmlMessage);
byte[] encodedXML = RaceVisionByteEncoder.encode(xmlMessage);
//Place the message in a packet.
BinaryMessageEncoder binaryMessageEncoder = new BinaryMessageEncoder(
@ -158,12 +158,13 @@ public class MockOutput implements Runnable
* Encodes/serialises a BoatLocation message, and returns it.
* @param boatLocation The BoatLocation message to serialise.
* @return The BoatLocation message in a serialised form.
* @throws InvalidMessageException If the message cannot be encoded.
*/
private synchronized byte[] parseBoatLocation(BoatLocation boatLocation){
private synchronized byte[] parseBoatLocation(BoatLocation boatLocation) throws InvalidMessageException {
//Encodes the message.
byte[] encodedBoatLoc = RaceVisionByteEncoder.boatLocation(boatLocation);
byte[] encodedBoatLoc = RaceVisionByteEncoder.encode(boatLocation);
//Encodes the full message with header.
BinaryMessageEncoder binaryMessageEncoder = new BinaryMessageEncoder(
@ -182,11 +183,12 @@ public class MockOutput implements Runnable
* Encodes/serialises a RaceStatus message, and returns it.
* @param raceStatus The RaceStatus message to serialise.
* @return The RaceStatus message in a serialised form.
* @throws InvalidMessageException Thrown if the message cannot be encoded.
*/
private synchronized byte[] parseRaceStatus(RaceStatus raceStatus){
private synchronized byte[] parseRaceStatus(RaceStatus raceStatus) throws InvalidMessageException {
//Encodes the messages.
byte[] encodedRaceStatus = RaceVisionByteEncoder.raceStatus(raceStatus);
byte[] encodedRaceStatus = RaceVisionByteEncoder.encode(raceStatus);
//Encodes the full message with header.
BinaryMessageEncoder binaryMessageEncoder = new BinaryMessageEncoder(
@ -209,7 +211,15 @@ public class MockOutput implements Runnable
public void sendHeartBeat() throws IOException {
//Sends a heartbeat every so often.
if (timeSinceHeartbeat() >= heartbeatPeriod) {
outToVisualiser.write(parseHeartbeat(createHeartbeatMessage()));
HeartBeat heartBeat = createHeartbeatMessage();
try {
outToVisualiser.write(parseHeartbeat(heartBeat));
} catch (InvalidMessageException e) {
Logger.getGlobal().log(Level.WARNING, "Could not encode HeartBeat: " + heartBeat, e);
}
lastHeartbeatTime = System.currentTimeMillis();
}
}
@ -219,39 +229,47 @@ public class MockOutput implements Runnable
*/
public void run() {
try {
while (!stop){
//Wait until all of the xml files have been set.
if (!this.latestMessages.hasAllXMLMessages()) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
continue;
}
//Wait until all of the xml files have been set.
while (!this.latestMessages.hasAllXMLMessages()) {
try {
Thread.sleep(500);
long previousFrameTime = System.currentTimeMillis();
boolean sentXMLs = false;
} catch (InterruptedException e) {
//If we get interrupted, exit the function.
Logger.getGlobal().log(Level.WARNING, "MockOutput.run().sleep(waitForXMLs) was interrupted on thread: " + Thread.currentThread(), e);
//Re-set the interrupt flag.
Thread.currentThread().interrupt();
return;
}
}
while(true) {
try {
long currentFrameTime = System.currentTimeMillis();
long previousFrameTime = System.currentTimeMillis();
boolean sentXMLs = false;
try {
while (!Thread.interrupted()) {
//This is the time elapsed, in milliseconds, since the last server "frame".
long framePeriod = currentFrameTime - previousFrameTime;
try {
//We only attempt to send packets every X milliseconds.
long minimumFramePeriod = 16;
if (framePeriod >= minimumFramePeriod) {
long currentFrameTime = System.currentTimeMillis();
//Send XML messages.
if (!sentXMLs) {
//Serialise them.
//This is the time elapsed, in milliseconds, since the last server "frame".
long framePeriod = currentFrameTime - previousFrameTime;
//We only attempt to send packets every X milliseconds.
long minimumFramePeriod = 16;
if (framePeriod >= minimumFramePeriod) {
//Send XML messages.
if (!sentXMLs) {
//Serialise them.
try {
byte[] raceXMLBlob = parseXMLMessage(latestMessages.getRaceXMLMessage());
byte[] regattaXMLBlob = parseXMLMessage(latestMessages.getRegattaXMLMessage());
byte[] boatsXMLBlob = parseXMLMessage(latestMessages.getBoatXMLMessage());
@ -261,55 +279,74 @@ public class MockOutput implements Runnable
outToVisualiser.write(regattaXMLBlob);
outToVisualiser.write(boatsXMLBlob);
sentXMLs = true;
} catch (InvalidMessageException e) {
Logger.getGlobal().log(Level.WARNING, "Could not encode XMLMessage: " + latestMessages.getRaceXMLMessage(), e);
continue; //Go to next iteration.
}
//Sends the RaceStatus message.
if (this.latestMessages.getRaceStatus() != null) {
}
//Sends the RaceStatus message.
if (this.latestMessages.getRaceStatus() != null) {
try {
byte[] raceStatusBlob = this.parseRaceStatus(this.latestMessages.getRaceStatus());
this.outToVisualiser.write(raceStatusBlob);
} catch (InvalidMessageException e) {
Logger.getGlobal().log(Level.WARNING, "Could not encode RaceStatus: " + latestMessages.getRaceStatus(), e);
}
}
//Send all of the BoatLocation messages.
for (int sourceID : this.latestMessages.getBoatLocationMap().keySet()) {
//Send all of the BoatLocation messages.
for (int sourceID : this.latestMessages.getBoatLocationMap().keySet()) {
//Get the message.
BoatLocation boatLocation = this.latestMessages.getBoatLocation(sourceID);
if (boatLocation != null) {
//Get the message.
BoatLocation boatLocation = this.latestMessages.getBoatLocation(sourceID);
if (boatLocation != null) {
try {
//Encode.
byte[] boatLocationBlob = this.parseBoatLocation(boatLocation);
//Write it.
this.outToVisualiser.write(boatLocationBlob);
} catch (InvalidMessageException e) {
Logger.getGlobal().log(Level.WARNING, "Could not encode BoatLocation: " + boatLocation, e);
}
}
previousFrameTime = currentFrameTime;
} else {
//Wait until the frame period will be large enough.
long timeToWait = minimumFramePeriod - framePeriod;
try {
Thread.sleep(timeToWait);
} catch (InterruptedException e) {
//If we get interrupted, exit the function.
e.printStackTrace();
//Re-set the interrupt flag.
Thread.currentThread().interrupt();
return;
}
}
previousFrameTime = currentFrameTime;
} else {
//Wait until the frame period will be large enough.
long timeToWait = minimumFramePeriod - framePeriod;
try {
Thread.sleep(timeToWait);
} catch (InterruptedException e) {
//If we get interrupted, exit the function.
Logger.getGlobal().log(Level.WARNING, "MockOutput.run().sleep(framePeriod) was interrupted on thread: " + Thread.currentThread(), e);
//Re-set the interrupt flag.
Thread.currentThread().interrupt();
return;
}
} catch (SocketException e) {
break;
}
} catch (SocketException e) {
break;
}
}
} catch (IOException e) {
@ -317,8 +354,5 @@ public class MockOutput implements Runnable
}
}
public void stop(){
stop = true;
}
}

@ -446,4 +446,4 @@ public class MockRace extends Race {
public List<CompoundMark> getCompoundMarks() {
return compoundMarks;
}
}
}

@ -2,9 +2,11 @@ package mock.model;
import network.Messages.BoatLocation;
import network.Messages.BoatStatus;
import network.Messages.Enums.BoatLocationDeviceEnum;
import network.Messages.LatestMessages;
import network.Messages.RaceStatus;
import network.Utils.AC35UnitConverter;
import shared.model.Bearing;
import shared.model.CompoundMark;
import shared.model.Constants;
import shared.model.Mark;
@ -47,7 +49,9 @@ public class RaceServer {
mark.getPosition().getLatitude(),
mark.getPosition().getLongitude(),
this.boatLocationSequenceNumber,
0, 0,
BoatLocationDeviceEnum.Mark,
Bearing.fromDegrees(0),
0,
race.getRaceClock().getCurrentTimeMilli());
//Iterates the sequence number.
@ -99,7 +103,8 @@ public class RaceServer {
boat.getCurrentPosition().getLatitude(),
boat.getCurrentPosition().getLongitude(),
this.boatLocationSequenceNumber,
boat.getBearing().degrees(),
BoatLocationDeviceEnum.RacingYacht,
boat.getBearing(),
boat.getCurrentSpeed(),
race.getRaceClock().getCurrentTimeMilli());
@ -133,19 +138,17 @@ public class RaceServer {
}
//Convert wind direction and speed to ints. //TODO this conversion should be done inside the racestatus class.
int windDirectionInt = AC35UnitConverter.encodeHeading(race.getWindDirection().degrees());
int windSpeedInt = (int) (race.getWindSpeed() * Constants.KnotsToMMPerSecond);
//Create race status object, and send it.
RaceStatus raceStatus = new RaceStatus(
RaceStatus.currentMessageVersionNumber,
System.currentTimeMillis(),
race.getRaceId(),
race.getRaceStatusEnum().getValue(),
race.getRaceStatusEnum(),
race.getRaceClock().getStartingTimeMilli(),
windDirectionInt,
windSpeedInt,
race.getRaceType().getValue(),
race.getWindDirection(),
race.getWindSpeed(),
race.getRaceType(),
boatStatuses);
this.latestMessages.setRaceStatus(raceStatus);

@ -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;
@ -16,42 +17,71 @@ import java.util.zip.CRC32;
*/
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;
@ -113,96 +143,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);
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));
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;
MessageType messageType = MessageType.fromByte(headerMessageType);
MessageDecoder decoder = null;
try {
decoder = DecoderFactory.create(messageType);
} 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);
}
}

@ -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;
AverageWind averageWind;
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);
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 class AverageWindDecoder implements MessageDecoder {
/**
* The encoded message.
*/
private byte[] encodedMessage;
/**
* The decoded message.
*/
private AverageWind message;
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,20 +1,56 @@
package network.MessageDecoders;
import network.Exceptions.InvalidMessageException;
import network.Messages.AC35Data;
import network.Messages.BoatAction;
import network.Messages.Enums.BoatActionEnum;
import java.util.Arrays;
public class BoatActionDecoder {
byte byteBoatAction;
BoatActionEnum boatAction;
/**
* Decodes {@link BoatAction} messages.
*/
public class BoatActionDecoder implements MessageDecoder {
public BoatActionDecoder(byte[] encodedBoatAction) {
byteBoatAction = encodedBoatAction[0];
/**
* The encoded message.
*/
private byte[] encodedMessage;
boatAction = BoatActionEnum.fromByte(byteBoatAction);
/**
* 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,142 +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.ByteConverter.bytesToInt;
import static network.Utils.ByteConverter.bytesToLong;
import static network.Utils.ByteConverter.bytesToShort;
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;
/**
* Constructs a decoder to decode a given message.
*/
public BoatLocationDecoder() {
}
@Override
public AC35Data decode(byte[] encodedMessage) throws InvalidMessageException {
this.encodedMessage = encodedMessage;
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){
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);
}
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));
}
/**
* 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;
messageVersionNumber = encodedCourseWind[0];
byteWindID = encodedCourseWind[1];
loopCount = encodedCourseWind[2];
byte[] loopMessagesBytes = Arrays.copyOfRange(encodedCourseWind, 3, lengthInBytesOfMessages*loopCount+3);
int messageLoopIndex = 0;
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;
}
}
public ArrayList<CourseWind> getLoopMessages() {
return loopMessages;
/**
* The encoded message.
*/
private byte[] encodedMessage;
/**
* The decoded message.
*/
private CourseWind message;
/**
* Constructs a decoder to decode a given message.
*/
public CourseWindDecoder() {
}
public byte getMessageVersionNumber() {
return messageVersionNumber;
/**
* 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 byte getByteWindID() {
return byteWindID;
/**
* Returns the decoded message.
* @return The decoded message.
*/
public CourseWind getMessage() {
return message;
}
}

@ -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;
MarkRounding markRounding;
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];
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);
public class MarkRoundingDecoder implements MessageDecoder {
/**
* The encoded message.
*/
private byte[] encodedMessage;
/**
* The decoded message.
*/
private MarkRounding message;
/**
* 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;
private long time;
private short ack;
private long startTime;
private int raceID;
private char notification;
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);
}
public class RaceStartStatusDecoder implements MessageDecoder {
/**
* The encoded message.
*/
private byte[] encodedMessage;
public byte getMessageVersion() {
return messageVersion;
}
/**
* The decoded message.
*/
private RaceStartStatus message;
public long getTime() {
return time;
}
public short getAck() {
return ack;
}
public long getStartTime() {
return startTime;
/**
* Constructs a decoder to decode a given message.
*/
public RaceStartStatusDecoder() {
}
public int getRaceID() {
return raceID;
@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 char getNotification() {
return notification;
/**
* Returns the decoded message.
* @return The decoded message.
*/
public RaceStartStatus getMessage() {
return message;
}
}

@ -1,10 +1,18 @@
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.bytesToInt;
import static network.Utils.ByteConverter.bytesToLong;
@ -12,110 +20,108 @@ import static network.Utils.ByteConverter.bytesToShort;
/**
* Created by hba56 on 21/04/17.
* Decodes {@link RaceStatus} messages.
*/
public class RaceStatusDecoder {
private byte versionNum;
private byte[] timeBytes;
private byte[] raceID;
private byte raceStatus;
private byte[] expectedStart;
private byte[] raceWind;
private byte[] windSpeed;
private byte numBoats;
private byte bytesRaceType;
private byte[] boatsBytes;
private long time;
private int race;
private byte raceState;
private long startTime;
private int raceWindDir;
private short raceWindSpeed;
private int numberOfBoats;
private int raceType;
private ArrayList<BoatStatus> boats = new ArrayList<>();
public RaceStatusDecoder(byte[] encodedRaceStatus){
versionNum = encodedRaceStatus[0];
timeBytes = Arrays.copyOfRange(encodedRaceStatus, 1, 7);
raceID = Arrays.copyOfRange(encodedRaceStatus, 7, 11);
raceStatus = encodedRaceStatus[11];
expectedStart = Arrays.copyOfRange(encodedRaceStatus, 12, 18);
raceWind = Arrays.copyOfRange(encodedRaceStatus, 18, 20);
windSpeed = Arrays.copyOfRange(encodedRaceStatus, 20, 22);
numBoats = encodedRaceStatus[22];
bytesRaceType = encodedRaceStatus[23];
boatsBytes = Arrays.copyOfRange(encodedRaceStatus, 24, 25+20*this.numBoats);
time = bytesToLong(timeBytes);
race = bytesToInt(raceID);
raceState = raceStatus;
startTime = bytesToLong(expectedStart);
raceWindDir = bytesToInt(raceWind);
raceWindSpeed = bytesToShort(windSpeed);
numberOfBoats = bytesToInt(numBoats);
int boatLoopIndex = 0;
for (int i=0; i < numberOfBoats; i++) {
byte[] boatBytes = Arrays.copyOfRange(boatsBytes, boatLoopIndex, boatLoopIndex+20);
byte[] sourceID = Arrays.copyOfRange(boatBytes, 0, 3);
byte boatStatus = boatBytes[4];
byte legNumber = boatBytes[5];
byte numPenaltiesAwarded = boatBytes[6];
byte numPenaltiesServed = boatBytes[7];
byte[] estTimeAtNextMark = Arrays.copyOfRange(boatBytes, 8, 14);
byte[] estTimeAtFinish = Arrays.copyOfRange(boatBytes, 14, 20);
BoatStatus boat = new BoatStatus(bytesToInt(sourceID),boatStatus,
legNumber, numPenaltiesAwarded, numPenaltiesServed,
bytesToLong(estTimeAtNextMark), bytesToLong(estTimeAtFinish));
boats.add(boat);
boatLoopIndex += 20;
}
}
public class RaceStatusDecoder implements MessageDecoder {
public byte getVersionNum() {
return versionNum;
}
/**
* The encoded message.
*/
private byte[] encodedMessage;
public long getTime() {
return time;
}
/**
* The decoded message.
*/
private RaceStatus message;
public int getRace() {
return race;
}
public byte getRaceState() {
return raceState;
}
public long getStartTime() {
return startTime;
/**
* Constructs a decoder to decode a given message.
*/
public RaceStatusDecoder() {
}
public int getRaceWindDir() {
return raceWindDir;
}
public short getRaceWindSpeed() {
return raceWindSpeed;
}
@Override
public AC35Data decode(byte[] encodedMessage) throws InvalidMessageException {
this.encodedMessage = encodedMessage;
public int getNumberOfBoats() {
return numberOfBoats;
}
try {
byte[] versionNumBytes = Arrays.copyOfRange(encodedMessage, 0, 1);
byte versionNum = versionNumBytes[0];
byte[] timeBytes = Arrays.copyOfRange(encodedMessage, 1, 7);
long time = bytesToLong(timeBytes);
byte[] raceIDBytes = Arrays.copyOfRange(encodedMessage, 7, 11);
int raceID = bytesToInt(raceIDBytes);
byte[] raceStatusBytes = Arrays.copyOfRange(encodedMessage, 11, 12);
RaceStatusEnum raceStatus = RaceStatusEnum.fromByte(raceStatusBytes[0]);
byte[] expectedStartBytes = Arrays.copyOfRange(encodedMessage, 12, 18);
long expectedStart = bytesToLong(expectedStartBytes);
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);
public int getRaceType() {
return raceType;
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 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,10 +1,10 @@
package network.MessageDecoders;
import network.Exceptions.InvalidMessageException;
import network.Messages.AC35Data;
import network.Messages.Enums.XMLMessageType;
import network.Messages.XMLMessage;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import static network.Utils.ByteConverter.bytesToLong;
@ -12,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;
private byte[] bytes;
public XMLMessageDecoder(byte[] bytes) {
this.bytes = bytes;
}
public class XMLMessageDecoder implements MessageDecoder {
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);
/**
* The encoded message.
*/
private byte[] encodedMessage;
this.xmlMsgSubType = bytes[9];
this.messageVersionNumber = bytes[0];
this.ackNumber = bytesToShort(ackNumberBytes);
/**
* The decoded message.
*/
private XMLMessage message;
this.timeStamp = bytesToLong(timeStampBytes);
this.sequenceNumber = bytesToShort(sequenceNumberBytes);
this.xmlMsgLength = bytesToShort(xmlMsgLengthBytes);
this.xmlMessage = new String(xmlMessagebytes).trim();
}
public byte getMessageVersionNumber() {
return messageVersionNumber;
/**
* Constructs a decoder to decode a given message.
*/
public XMLMessageDecoder() {
}
public short getAckNumber() {
return ackNumber;
}
public long getTimeStamp() {
return timeStamp;
}
@Override
public AC35Data decode(byte[] encodedMessage) throws InvalidMessageException {
this.encodedMessage = encodedMessage;
public XMLMessageType getXmlMsgSubType() {
return XMLMessageType.fromByte(xmlMsgSubType);
}
try {
public short getSequenceNumber() {
return sequenceNumber;
}
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);
public short getXmlMsgLength() {
return xmlMsgLength;
}
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 contents of the XML message (e.g., the contents of a race.xml file).
* @return The contents of the XML message.
* Returns the decoded message.
* @return The decoded message.
*/
public String getXmlMessageContents() {
return xmlMessage;
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,10 @@
package network.MessageEncoders;
import network.Exceptions.InvalidMessageException;
import network.Exceptions.InvalidMessageTypeException;
import network.Messages.*;
import network.Messages.Enums.MessageType;
import static network.Utils.ByteConverter.*;
@ -17,72 +20,8 @@ import java.util.List;
*/
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);
@ -125,25 +64,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;
@ -181,169 +101,29 @@ public class RaceVisionByteEncoder {
}
/**
* Serializes an xml message into a byte array.
* @param xmlMessage The message to serialize.
* @return byte array contaning serialized message.
*/
public static byte[] xmlMessage(XMLMessage xmlMessage) {
byte[] messageBytes = xmlMessage.getXmlMessage().getBytes(StandardCharsets.UTF_8);
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();
}
/**
* Encodes a given message.
* @param message Message to encode.
* @return Encoded message.
* @throws InvalidMessageException If the message cannot be encoded.
*/
public static byte[] encode(AC35Data message) throws InvalidMessageException {
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();
}
MessageEncoder encoder = null;
try {
encoder = EncoderFactory.create(message.getType());
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();
}
} catch (InvalidMessageTypeException e) {
throw new InvalidMessageException("Could not create encoder for MessageType: " + message.getType(), e);
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();
}
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();
}
byte[] encodedMessage = encoder.encode(message);
public static byte[] boatActionMessage(BoatAction boatAction){
ByteBuffer boatActionMessage = ByteBuffer.allocate(1);
boatActionMessage.put(intToBytes(boatAction.getBoatAction(), 1));
byte [] result = boatActionMessage.array();
return result;
return encodedMessage;
}
}

@ -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);
}
}
}

@ -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;
public AverageWind(int msgNum, long lngTime, int rawPeriod, int rawSpeed, int period2, int speed2, int period3, int speed3, int period4, 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 ) {
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,30 @@ 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;
/**
* 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;
}
}
}

@ -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;
/**
* Latitude of the boat.
*/
private double latitude;
///Altitude of the boat.
/**
* 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;
/**
* Speed of the boat, in knots.
*/
private double boatSpeedKnots;
///Apparent wind speed at time of event. Proper type is unsigned 2 byte int. millimeters per second.
private int apparentWindSpeed;
/**
* Course over ground (COG) of the boat.
*/
private Bearing boatCOG;
///Apparent wind angle at time of the event. Wind over starboard = positive.
private short apparentWindAngle;
/**
* Speed over ground (SOG) of the boat, in knots.
*/
private double boatSOGKnots;
///True wind speed. Proper type is unsigned 2 byte int. millimeters per second.
private int trueWindSpeed;
/**
* Apparent wind speed at time of event. Proper type is unsigned 2 byte int. millimeters per second.
*/
private double apparentWindSpeedKnots;
///True wind direction. Proper type is unsigned 2 byte int. 0x0000 = North, etc..
private int trueWindDirection;
/**
* Apparent wind angle at time of the event. Wind over starboard = positive.
*/
private Azimuth apparentWindAngle;
///True wind angle. Clockwise compass direction, 0 = north.
private short trueWindAngle;
/**
* True wind speed, in knots.
*/
private double trueWindSpeedKnots;
///Current drift. Proper type is unsigned 2 byte int. millimeters per second.
private int currentDrift;
/**
* True wind direction.
*/
private Bearing trueWindDirection;
///Current set. Proper type is unsigned 2 byte int. Clockwise compass direction, 0 = north.
private int currentSet;
/**
* True wind angle. Clockwise compass direction, 0 = north.
*/
private Azimuth trueWindAngle;
///Rudder angle. Positive is rudder set to turn yacht to port.
private short rudderAngle;
/**
* Current drift, in knots.
*/
private double currentDriftKnots;
/**
* Current set.
*/
private Bearing currentSet;
/**
* Ctor. Default.
* Rudder angle. Positive is rudder set to turn yacht to port.
*/
public BoatLocation() {
super(MessageType.BOATLOCATION);
}
private Azimuth rudderAngle;
/**
* Ctor, with all parameters.
* 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;
}
//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.
*/
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;
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) );
}
/**
* 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).
* Returns the version number of the message.
* @return The version number of the message.
*/
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;
}
public int getBoatSpeed() {
return boatSpeed;
/**
* Returns the current boat speed, in knots.
* @return Current boat speed, in knots.
*/
public double getBoatSpeedKnots() {
return boatSpeedKnots;
}
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;
}
public int getBoatSOG() {
return boatSOG;
}
public void setBoatSOG(int boatSOG) {
this.boatSOG = boatSOG;
/**
* Returns the boats Speed Over Ground, in knots.
* @return Boat's SOG.
*/
public double getBoatSOGKnots() {
return boatSOGKnots;
}
public int getApparentWindSpeed() {
return apparentWindSpeed;
}
public void setApparentWindSpeed(int apparentWindSpeed) {
this.apparentWindSpeed = apparentWindSpeed;
/**
* Returns the apparent wind speed, in knots, at the boat.
* @return Wind speed, in knots, at the boat.
*/
public double getApparentWindSpeedKnots() {
return apparentWindSpeedKnots;
}
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;
}
public int getTrueWindSpeed() {
return trueWindSpeed;
/**
* Returns the true wind speed, in knots.
* @return True wind speed, in knots.
*/
public double getTrueWindSpeedKnots() {
return trueWindSpeedKnots;
}
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;
}
public int getCurrentDrift() {
return currentDrift;
/**
* Returns the current drift of the boat, in knots.
* @return Current drift, in knots.
*/
public double getCurrentDriftKnots() {
return currentDriftKnots;
}
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 he 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 he 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,102 @@
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.
*/
JOIN_SUCCESSFUL(1),
/**
* The race is full - no more participants allowed.
*/
RACE_PARTICIPANTS_FULL(2),
/**
* The race cannot allow any more ghost participants to join.
*/
GHOST_PARTICIPANTS_FULL(3),
/**
* The server is completely full, cannot participate or spectate.
*/
SERVER_FULL(4),
/**
* 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,29 @@ public enum MessageType {
MARKROUNDING(38),
COURSEWIND(44),
AVGWIND(47),
/**
* This is used for {@link network.Messages.RequestToJoin} messages.
*/
REQUEST_TO_JOIN(55),
/**
* This is used for {@link network.Messages.JoinAcceptance} messages.
*/
JOIN_ACCEPTANCE(56),
BOATACTION(100),
NOTAMESSAGE(0);
///Primitive value of the enum.
/**
* Primitive value of the enum.
*/
private byte value;
/**
* Ctor. Creates a MessageType enum from a given primitive integer value, cast to a byte.
* Creates a MessageType enum from a given primitive integer value, cast to a byte.
* @param value Integer, which is cast to byte, to construct from.
*/
private MessageType(int value) {

@ -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,97 @@
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 particpate as a ghost.
*/
GHOST(5),
/**
* 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;
}
}

@ -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;
public static int RoundingSideUnknown = 0;
public static int RoundingSidePort = 1;
public static int RoundingSideStarboard = 2;
public static int MarkTypeUnknown = 0;
public static int MarkTypeRoundingMark = 1;
public static int MarkTypeGate = 2;
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;
public MarkRounding(int msgVerNum, long time, int ackNum, int raceID, int sourceID, int boatStatus, int roundingSide, int markType, int markID){
/**
* The status of the boat.
*/
private MarkRoundingBoatStatusEnum boatStatus;
/**
* The side around which the boat rounded.
*/
private MarkRoundingSideEnum roundingSide;
/**
* The type of mark that was rounded.
*/
private MarkRoundingTypeEnum markType;
/**
* 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 ) {
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;
}
}

@ -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 {
/**
* The current version number of this message type.
*/
public static final byte currentMessageVersionNumber = 1;
/**
* 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;
private int notificationType;
public RaceStartStatus(long timestamp, int ackNum, long raceStartTime, int raceID, int notificationType){
/**
* 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.
* Returns the wind speed for this race status, in knots.
* @return Wind speed in knots.
*/
public int getWindSpeed()
public double getWindSpeed()
{
return windSpeed;
}
public int getRaceType()
/**
* Retrusn the type of race this is.
* @return The type of race this is.
*/
public RaceTypeEnum getRaceType()
{
return raceType;
}
/**
* Returns the list of BoatStatuses. One for each boat.
* @return List of BoatStatuses.
*/
public List<BoatStatus> getBoatStatuses()
{
return boatStatuses;
}
public boolean isNotActive() {
return raceStatus == 0;
return raceStatus == RaceStatusEnum.NOT_ACTIVE;
}
public boolean isWarning() {
return raceStatus == 1;
return raceStatus == RaceStatusEnum.WARNING;
}
public boolean isPreparatory() {
return raceStatus == 2;
return raceStatus == RaceStatusEnum.PREPARATORY;
}
public boolean isStarted() {
return raceStatus == 3;
return raceStatus == RaceStatusEnum.STARTED;
}
public boolean isFinished() {
return raceStatus == 4;
return raceStatus == RaceStatusEnum.FINISHED;
}
public boolean isRetired() {
return raceStatus == 5;
return raceStatus == RaceStatusEnum.RETIRED;
}
public boolean isAbandoned() {
return raceStatus == 6;
return raceStatus == RaceStatusEnum.ABANDONED;
}
public boolean isPostponed() {
return raceStatus == 7;
return raceStatus == RaceStatusEnum.POSTPONED;
}
public boolean isTerminated() {
return raceStatus == 8;
return raceStatus == RaceStatusEnum.TERMINATED;
}
public boolean isStartTimeSet() {
return raceStatus != 9;
return raceStatus != RaceStatusEnum.RACE_START_TIME_NOT_SET;
}
public boolean isPrestart() {
return raceStatus == 10;
return raceStatus == RaceStatusEnum.PRESTART;
}
public double getScaledWindDirection() {
return AC35UnitConverter.convertHeading(windDirection);
}
/**
* Returns the wind speed for this race status, in knots.
* @return Wind speed in knots.
*/
public double getWindSpeedKnots() {
return (windSpeed / Constants.KnotsToMMPerSecond);
}
}

@ -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;
}
}

@ -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
/**
* 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(long value){
return (double) 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 double convertHeading(int value){
return (double) value * 360.0/65536.0;//2^15
/**
* 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 double convertHeading(double value){
return 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 int encodeHeading(int value){
return (int) (value / 360.0 * 65536.0);//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
}
public static int encodeHeading(double value){
return (int) (value / 360.0 * 65536.0);//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);
}
public static double convertTrueWindAngle(long value){
return (double) value * 180.0/32768.0;//-2^15 to 2^15
/**
* 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);
}
}

@ -1,26 +1,13 @@
package visualiser.app;
import javafx.application.Platform;
import network.BinaryMessageDecoder;
import network.Exceptions.InvalidMessageException;
import network.Messages.*;
import org.xml.sax.SAXException;
import shared.dataInput.BoatXMLReader;
import shared.dataInput.RaceXMLReader;
import shared.dataInput.RegattaXMLReader;
import shared.exceptions.InvalidBoatDataException;
import shared.exceptions.InvalidRaceDataException;
import shared.exceptions.InvalidRegattaDataException;
import shared.exceptions.XMLReaderException;
import javax.xml.parsers.ParserConfigurationException;
import java.io.DataInputStream;
import java.io.IOException;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ArrayBlockingQueue;
import static network.Utils.ByteConverter.bytesToShort;
@ -213,12 +200,12 @@ public class VisualiserInput implements Runnable {
//Heartbeat.
case HEARTBEAT: {
Heartbeat heartbeat = (Heartbeat) message;
HeartBeat heartBeat = (HeartBeat) message;
//Check that the heartbeat number is greater than the previous value, and then set the last heartbeat time.
if (heartbeat.getSequenceNumber() > this.lastHeartbeatSequenceNum) {
if (heartBeat.getSequenceNumber() > this.lastHeartbeatSequenceNum) {
lastHeartbeatTime = System.currentTimeMillis();
lastHeartbeatSequenceNum = heartbeat.getSequenceNumber();
lastHeartbeatSequenceNum = heartBeat.getSequenceNumber();
//System.out.println("HeartBeat Message! " + lastHeartbeatSequenceNum);
}

@ -1,6 +1,7 @@
package visualiser.gameController;
import network.BinaryMessageEncoder;
import network.Exceptions.InvalidMessageException;
import network.MessageEncoders.RaceVisionByteEncoder;
import network.Messages.BoatAction;
import network.Messages.Enums.BoatActionEnum;
@ -12,6 +13,8 @@ import java.io.IOException;
import java.net.Socket;
import java.net.SocketException;
import java.nio.ByteBuffer;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Basic service for sending key presses to game server
@ -50,19 +53,24 @@ public class ControllerClient {
BoatActionEnum protocolCode = key.getProtocolCode();
if(protocolCode != BoatActionEnum.NOT_A_STATUS) {
byte[] bytes = new byte[4];
ByteBuffer.wrap(bytes).putInt(protocolCode.getValue());
BoatActionEnum boatActionEnum = BoatActionEnum.fromByte(bytes[3]);
BoatAction boatAction = new BoatAction(protocolCode);
BoatAction boatAction = new BoatAction(boatActionEnum);
//Encode BoatAction.
try {
byte[] encodedBoatAction = RaceVisionByteEncoder.encode(boatAction);
byte[] encodedBoatAction = RaceVisionByteEncoder.boatActionMessage(boatAction);
BinaryMessageEncoder binaryMessage = new BinaryMessageEncoder(MessageType.BOATACTION, System.currentTimeMillis(), 0,
(short) encodedBoatAction.length, encodedBoatAction);
System.out.println("Sending out key: " + protocolCode);
outputStream.write(binaryMessage.getFullMessage());
} catch (InvalidMessageException e) {
Logger.getGlobal().log(Level.WARNING, "Could not encode BoatAction: " + boatAction, e);
}
BinaryMessageEncoder binaryMessage = new BinaryMessageEncoder(MessageType.BOATACTION, System.currentTimeMillis(), 0,
(short) encodedBoatAction.length, encodedBoatAction);
System.out.println("Sending out key: " + boatActionEnum);
outputStream.write(binaryMessage.getFullMessage());
}
}
}

@ -2,13 +2,17 @@ package visualiser.gameController;
import mock.model.RaceLogic;
import network.BinaryMessageDecoder;
import network.Exceptions.InvalidMessageException;
import network.MessageDecoders.BoatActionDecoder;
import network.Messages.BoatAction;
import network.Messages.Enums.BoatActionEnum;
import java.io.DataInputStream;
import java.io.IOException;
import java.net.Socket;
import java.util.logging.Level;
import java.util.Observable;
import java.util.logging.Logger;
/**
* Service for dispatching key press data to race from client
@ -59,14 +63,26 @@ public class ControllerServer extends Observable implements Runnable {
byte[] message = new byte[20];
try {
if (inputStream.available() > 0) {
inputStream.read(message);
BinaryMessageDecoder encodedMessage = new BinaryMessageDecoder(message);
BoatActionDecoder boatActionDecoder = new BoatActionDecoder(encodedMessage.getMessageBody());
action = boatActionDecoder.getBoatAction();
BoatActionDecoder boatActionDecoder = new BoatActionDecoder();
try {
boatActionDecoder.decode(encodedMessage.getMessageBody());
BoatAction boatAction = boatActionDecoder.getMessage();
action = boatAction.getBoatAction();
// Notify observers of most recent action
this.notifyObservers();
this.setChanged();
} catch (InvalidMessageException e) {
Logger.getGlobal().log(Level.WARNING, "Could not decode BoatAction message.", e);
}
// Notify observers of most recent action
this.notifyObservers();
this.setChanged();
}
} catch (IOException e) {
e.printStackTrace();

@ -16,7 +16,6 @@ import shared.dataInput.RegattaDataSource;
import shared.model.*;
import java.time.Duration;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@ -198,24 +197,24 @@ public class VisualiserRace extends Race implements Runnable {
if (boatLocation != null && boatStatus != null) {
//Get the new position.
double latitude = boatLocation.getLatitudeDouble();
double longitude = boatLocation.getLongitudeDouble();
double latitude = boatLocation.getLatitude();
double longitude = boatLocation.getLongitude();
GPSCoordinate gpsCoordinate = new GPSCoordinate(latitude, longitude);
boat.setCurrentPosition(gpsCoordinate);
//Bearing.
boat.setBearing(Bearing.fromDegrees(boatLocation.getHeadingDegrees()));
boat.setBearing(boatLocation.getHeading());
//Time until next mark.
boat.setEstimatedTimeAtNextMark(raceClock.getLocalTime(boatStatus.getEstTimeAtNextMark()));
//Speed.
boat.setCurrentSpeed(boatLocation.getBoatSOG() / Constants.KnotsToMMPerSecond);
boat.setCurrentSpeed(boatLocation.getBoatSpeedKnots());
//Boat status.
BoatStatusEnum newBoatStatusEnum = BoatStatusEnum.fromByte(boatStatus.getBoatStatus());
BoatStatusEnum newBoatStatusEnum = boatStatus.getBoatStatus();
//If we are changing from non-racing to racing, we need to initialise boat with their time at last mark.
if ((boat.getStatus() != BoatStatusEnum.RACING) && (newBoatStatusEnum == BoatStatusEnum.RACING)) {
@ -292,8 +291,8 @@ public class VisualiserRace extends Race implements Runnable {
if (boatLocation != null) {
//We only update the boat's position.
double latitude = boatLocation.getLatitudeDouble();
double longitude = boatLocation.getLongitudeDouble();
double latitude = boatLocation.getLatitude();
double longitude = boatLocation.getLongitude();
GPSCoordinate gpsCoordinate = new GPSCoordinate(latitude, longitude);
mark.setPosition(gpsCoordinate);
@ -309,12 +308,12 @@ public class VisualiserRace extends Race implements Runnable {
private void updateRaceStatus(RaceStatus raceStatus) {
//Race status enum.
this.raceStatusEnum = RaceStatusEnum.fromByte(raceStatus.getRaceStatus());
this.raceStatusEnum = raceStatus.getRaceStatus();
//Wind.
this.setWind(
Bearing.fromDegrees(raceStatus.getScaledWindDirection()),
raceStatus.getWindSpeedKnots() );
raceStatus.getWindDirection(),
raceStatus.getWindSpeed() );
//Current race time.
this.raceClock.setUTCTime(raceStatus.getCurrentTime());

@ -2,6 +2,7 @@ package network;
import network.Exceptions.InvalidMessageException;
import network.MessageDecoders.XMLMessageDecoder;
import network.MessageDecoders.XMLMessageDecoderTest;
import network.MessageEncoders.RaceVisionByteEncoder;
import network.Messages.AC35Data;
import network.Messages.Enums.MessageType;
@ -21,29 +22,47 @@ import java.nio.charset.StandardCharsets;
import static org.junit.Assert.fail;
/**
* Created by hba56 on 21/04/17.
* Tests the binary message decoder and encoder for a variety of messages.
*/
public class BinaryMessageDecoderTest {
/**
* Tests if an XMLMessage can be encoded and decoded correctly.
* @throws Exception if test fails.
*/
@Test
public void decodeTest(){
try{
String xmlString = XMLReader.readXMLFileToString("network/raceXML/Regatta.xml", StandardCharsets.UTF_8);
public void xmlMessageTest() throws Exception {
try {
String filePath = "network/raceXML/Regatta.xml";
XMLMessageType messageType = XMLMessageType.REGATTA;
String xmlString = XMLReader.readXMLFileToString(filePath, StandardCharsets.UTF_8);
long time = System.currentTimeMillis();
XMLMessage xmlMessagePre = new XMLMessage(
XMLMessage xmlMessage = new XMLMessage(
(byte)1,
1,
time,
XMLMessageType.REGATTA,
messageType,
(short)1,
xmlString );
xmlString );
byte[] encodedMessage = RaceVisionByteEncoder.encode(xmlMessage);
byte[] encodedMessage = RaceVisionByteEncoder.xmlMessage(xmlMessagePre);
BinaryMessageEncoder encoder = new BinaryMessageEncoder(
xmlMessage.getType(),
time,
1,
(short)encodedMessage.length,
encodedMessage );
BinaryMessageEncoder testMessage = new BinaryMessageEncoder(MessageType.XMLMESSAGE, time, 1, (short)encodedMessage.length, encodedMessage);
BinaryMessageDecoder decoder = new BinaryMessageDecoder(encoder.getFullMessage());
BinaryMessageDecoder decoder = new BinaryMessageDecoder(testMessage.getFullMessage());
AC35Data message = null;
try {
@ -56,7 +75,7 @@ public class BinaryMessageDecoderTest {
if (!(message instanceof XMLMessage)){
Assert.assertFalse(true);
}
XMLMessage xmlMessage = (XMLMessage) message;
XMLMessage xmlMessageDecoded = (XMLMessage) message;
//message length
@ -66,29 +85,17 @@ public class BinaryMessageDecoderTest {
//source ID
Assert.assertEquals((short) 1, decoder.getHeaderSourceID());
//message type
Assert.assertEquals(26, decoder.getHeaderMessageType());
XMLMessageDecoder decoderXML = new XMLMessageDecoder(decoder.getMessageBody());
decoderXML.decode();
//tests from seng302.Networking.MessageDecoders.XMLMessageDecoderTest to make sure the file is still good
Assert.assertEquals((byte)1, decoderXML.getMessageVersionNumber());
Assert.assertEquals((short)1, decoderXML.getAckNumber());
Assert.assertEquals(time, decoderXML.getTimeStamp());
Assert.assertEquals(XMLMessageType.REGATTA, decoderXML.getXmlMsgSubType());
Assert.assertEquals((short)1, decoderXML.getSequenceNumber());
Assert.assertEquals((short)xmlString.length(), decoderXML.getXmlMsgLength());
// Reader reader = decoderXML.getXmlMessageInputStream().getCharacterStream();
// int c;
// String contents = "";
// while((c = reader.read()) != -1) {
// contents += (char)c;
// }
// Assert.assertEquals(xmlString.toString(), contents);
Assert.assertEquals(MessageType.XMLMESSAGE.getValue(), decoder.getHeaderMessageType());
XMLMessageDecoderTest.compareXMLMessages(xmlMessage, xmlMessageDecoded);
} catch (XMLReaderException | TransformerException e){
fail("couldn't read file" + e.getMessage());
}
}
//TODO add some tests for more messages types.
}

@ -0,0 +1,75 @@
package network.MessageDecoders;
import network.MessageEncoders.RaceVisionByteEncoder;
import network.Messages.AverageWind;
import static org.junit.Assert.*;
import org.junit.Test;
import shared.model.Bearing;
import java.util.ArrayList;
/**
* Test for the AverageWind encoder and decoder
*/
public class AverageWindDecoderTest {
/**
* Creates a AverageWind message, encodes it, decodes it, and checks that the result matches the starting message.
* @throws Exception if test fails.
*/
@Test
public void averageWindEncodeDecodeTest() throws Exception {
AverageWind averageWind = new AverageWind(
AverageWind.currentMessageVersionNumber,
System.currentTimeMillis(),
3000,
12.5,
4050,
12.6,
3055,
12.7,
6051,
13.37
);
byte[] encodedMessage = RaceVisionByteEncoder.encode(averageWind);
AverageWindDecoder averageWindDecoder = new AverageWindDecoder();
averageWindDecoder.decode(encodedMessage);
AverageWind averageWindDecoded = averageWindDecoder.getMessage();
compareAverageWindMessages(averageWind, averageWindDecoded);
}
/**
* Compares two AverageWind messages to check that they are equal.
* @param original The original AverageWind message.
* @param decoded The decoded AverageWind message.
*/
public static void compareAverageWindMessages(AverageWind original, AverageWind decoded) {
assertEquals(original.getMessageVersionNumber(), decoded.getMessageVersionNumber());
assertEquals(original.getTime(), decoded.getTime());
assertEquals(original.getRawPeriod(), decoded.getRawPeriod(), 100);
assertEquals(original.getRawSpeedKnots(), decoded.getRawSpeedKnots(), 0.01);
assertEquals(original.getSampleTwoPeriod(), decoded.getSampleTwoPeriod(), 100);
assertEquals(original.getSampleTwoSpeedKnots(), decoded.getSampleTwoSpeedKnots(), 0.01);
assertEquals(original.getSampleThreePeriod(), decoded.getSampleThreePeriod(), 100);
assertEquals(original.getSampleThreeSpeedKnots(), decoded.getSampleThreeSpeedKnots(), 0.01);
assertEquals(original.getSampleFourPeriod(), decoded.getSampleFourPeriod(), 100);
assertEquals(original.getSampleFourSpeedKnots(), decoded.getSampleFourSpeedKnots(), 0.01);
}
}

@ -0,0 +1,117 @@
package network.MessageDecoders;
import network.Exceptions.InvalidMessageException;
import network.MessageEncoders.RaceVisionByteEncoder;
import network.Messages.BoatAction;
import network.Messages.Enums.BoatActionEnum;
import network.Messages.Enums.RequestToJoinEnum;
import network.Messages.RequestToJoin;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
/**
* Test for the BoatAction encoder and decoder
*/
public class BoatActionDecoderTest {
/**
* Encodes and decodes a given message.
* @param message Message to encode/decode.
* @return The decoded message.
* @throws InvalidMessageException If the message cannot be encoded.
*/
private BoatAction encodeDecodeMessage(BoatAction message) throws InvalidMessageException {
//Encode.
byte [] testEncodedMessage = RaceVisionByteEncoder.encode(message);
//Decode.
BoatActionDecoder testDecoder = new BoatActionDecoder();
testDecoder.decode(testEncodedMessage);
BoatAction decodedMessage = testDecoder.getMessage();
return decodedMessage;
}
/**
* Tests if a specific boat action type message can be encoded and decoded correctly.
* @param type The type of boat action.
* @throws Exception if test fails.
*/
private void boatActionTypeTest(BoatActionEnum type) throws Exception {
//Prepare message.
BoatAction beforeMessage = new BoatAction(type);
//Encode/decode it.
BoatAction afterMessage = encodeDecodeMessage(beforeMessage);
//Compare.
assertEquals(beforeMessage.getBoatAction(), afterMessage.getBoatAction());
}
/**
* Tests if an autopilot message can be encoded and decoded correctly.
* @throws Exception if test fails.
*/
@Test
public void autoPilotTest() throws Exception {
boatActionTypeTest(BoatActionEnum.AUTO_PILOT);
}
/**
* Tests if a sails in message can be encoded and decoded correctly.
* @throws Exception if test fails.
*/
@Test
public void sailsInTest() throws Exception {
boatActionTypeTest(BoatActionEnum.SAILS_IN);
}
/**
* Tests if a sails out message can be encoded and decoded correctly.
* @throws Exception if test fails.
*/
@Test
public void sailsOutTest() throws Exception {
boatActionTypeTest(BoatActionEnum.SAILS_OUT);
}
/**
* Tests if a tack/gybe message can be encoded and decoded correctly.
* @throws Exception if test fails.
*/
@Test
public void tackGybeTest() throws Exception {
boatActionTypeTest(BoatActionEnum.TACK_GYBE);
}
/**
* Tests if an upwind message can be encoded and decoded correctly.
* @throws Exception if test fails.
*/
@Test
public void upwindTest() throws Exception {
boatActionTypeTest(BoatActionEnum.UPWIND);
}
/**
* Tests if a downwind message can be encoded and decoded correctly.
* @throws Exception if test fails.
*/
@Test
public void downwindTest() throws Exception {
boatActionTypeTest(BoatActionEnum.DOWNWIND);
}
}

@ -2,48 +2,82 @@ package network.MessageDecoders;
import network.MessageEncoders.RaceVisionByteEncoder;
import network.Messages.BoatLocation;
import network.Messages.Enums.BoatLocationDeviceEnum;
import org.junit.Assert;
import org.junit.Test;
import shared.model.Azimuth;
import shared.model.Bearing;
/**
* Created by hba56 on 23/04/17.
* Test for the BoatLocation encoder and decoder
*/
public class BoatLocationDecoderTest {
/**
* Creates a BoatLocation message, encodes it, decodes it, and checks that the result matches the starting message.
* @throws Exception if test fails.
*/
@Test
public void getByteArrayTest(){
public void boatLocationEncodeDecodeTest() throws Exception {
//Create message.
long time = System.currentTimeMillis();
BoatLocation testMessage = new BoatLocation((byte) 1, time, 2,
3, (byte) 1, 180, -180, 4, 5,
(short) 6, (short) 7, 8, 9, 10, 11,
(short) 12, 13, 14 , (short) 15,
16, 17, (short) 18);
byte [] testEncodedMessage = RaceVisionByteEncoder.boatLocation(testMessage);
BoatLocation testMessage = new BoatLocation(
BoatLocation.currentMessageVersionNumber,
time,
2,
3,
BoatLocationDeviceEnum.RacingYacht,
180,
-180,
4,
Bearing.fromDegrees(45),
(short) 6,
(short) 7,
8,
Bearing.fromDegrees(40),
10,
11,
Azimuth.fromDegrees(35),
13,
Bearing.fromDegrees(80),
Azimuth.fromDegrees(80),
16,
Bearing.fromDegrees(80),
Azimuth.fromDegrees(22) );
BoatLocationDecoder testDecoder = new BoatLocationDecoder(testEncodedMessage);
//Encode.
byte [] testEncodedMessage = RaceVisionByteEncoder.encode(testMessage);
//Decode.
BoatLocationDecoder testDecoder = new BoatLocationDecoder();
testDecoder.decode(testEncodedMessage);
BoatLocation decodedTest = testDecoder.getMessage();
//Check if valid.
Assert.assertEquals(testMessage.getMessageVersionNumber(), decodedTest.getMessageVersionNumber());
Assert.assertEquals(testMessage.getTime(), decodedTest.getTime());
Assert.assertEquals(testMessage.getSequenceNumber(), decodedTest.getSequenceNumber());
Assert.assertEquals(testMessage.getDeviceType(), decodedTest.getDeviceType());
Assert.assertEquals(testMessage.getLatitude(), decodedTest.getLatitude());
Assert.assertEquals(testMessage.getLongitude(), decodedTest.getLongitude());
Assert.assertEquals(testMessage.getLatitude(), decodedTest.getLatitude(), 0.01);
Assert.assertEquals(testMessage.getLongitude(), decodedTest.getLongitude(), 0.01);
Assert.assertEquals(testMessage.getAltitude(), decodedTest.getAltitude());
Assert.assertEquals(testMessage.getHeading(), decodedTest.getHeading());
Assert.assertEquals(testMessage.getHeading().degrees(), decodedTest.getHeading().degrees(), 0.01);
Assert.assertEquals(testMessage.getPitch(), decodedTest.getPitch());
Assert.assertEquals(testMessage.getRoll(), decodedTest.getRoll());
Assert.assertEquals(testMessage.getBoatSpeed(), decodedTest.getBoatSpeed());
Assert.assertEquals(testMessage.getBoatCOG(), decodedTest.getBoatCOG());
Assert.assertEquals(testMessage.getBoatSOG(), decodedTest.getBoatSOG());
Assert.assertEquals(testMessage.getApparentWindSpeed(), decodedTest.getApparentWindSpeed());
Assert.assertEquals(testMessage.getTrueWindSpeed(), decodedTest.getTrueWindSpeed());
Assert.assertEquals(testMessage.getTrueWindDirection(), decodedTest.getTrueWindDirection());
Assert.assertEquals(testMessage.getTrueWindAngle(), decodedTest.getTrueWindAngle());
Assert.assertEquals(testMessage.getCurrentDrift(), decodedTest.getCurrentDrift());
Assert.assertEquals(testMessage.getCurrentSet(), decodedTest.getCurrentSet());
Assert.assertEquals(testMessage.getRudderAngle(), decodedTest.getRudderAngle());
Assert.assertEquals(testMessage.getBoatSpeedKnots(), decodedTest.getBoatSpeedKnots(), 0.01);
Assert.assertEquals(testMessage.getBoatCOG().degrees(), decodedTest.getBoatCOG().degrees(), 0.01);
Assert.assertEquals(testMessage.getBoatSOGKnots(), decodedTest.getBoatSOGKnots(), 0.01);
Assert.assertEquals(testMessage.getApparentWindSpeedKnots(), decodedTest.getApparentWindSpeedKnots(), 0.01);
Assert.assertEquals(testMessage.getTrueWindSpeedKnots(), decodedTest.getTrueWindSpeedKnots(), 0.01);
Assert.assertEquals(testMessage.getTrueWindDirection().degrees(), decodedTest.getTrueWindDirection().degrees(), 0.01);
Assert.assertEquals(testMessage.getTrueWindAngle().degrees(), decodedTest.getTrueWindAngle().degrees(), 0.01);
Assert.assertEquals(testMessage.getCurrentDriftKnots(), decodedTest.getCurrentDriftKnots(), 0.01);
Assert.assertEquals(testMessage.getCurrentSet().degrees(), decodedTest.getCurrentSet().degrees(), 0.01);
Assert.assertEquals(testMessage.getRudderAngle().degrees(), decodedTest.getRudderAngle().degrees(), 0.01);
}
}

@ -0,0 +1,90 @@
package network.MessageDecoders;
import network.Exceptions.InvalidMessageException;
import network.MessageEncoders.BoatStatusEncoder;
import network.MessageEncoders.RaceVisionByteEncoder;
import network.Messages.BoatStatus;
import network.Messages.Enums.BoatStatusEnum;
import org.junit.Assert;
import org.junit.Test;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* Test for the BoatStatus encoder and decoder
*/
public class BoatStatusDecoderTest {
/**
* Creates a BoatStatus message, encodes it, decodes it, and checks that the result matches the starting message.
* @throws Exception if test fails.
*/
@Test
public void boatStatusEncodeDecodeTest() throws Exception {
long time = System.currentTimeMillis();
//Create data to serialize.
int boatSourceID = 5;
BoatStatusEnum boatStatusEnum = BoatStatusEnum.RACING;
byte boatLegNumber = 5;
byte boatPenaltiesAwarded = 4;
byte boatPenaltiesServed = 2;
long boatTimeAtNextMark = time + (1000 * 3);
long boatTimeAtFinish = boatTimeAtNextMark + (1000 * 15);
BoatStatus boatStatus = new BoatStatus(
boatSourceID,
boatStatusEnum,
boatLegNumber,
boatPenaltiesAwarded,
boatPenaltiesServed,
boatTimeAtNextMark,
boatTimeAtFinish );
BoatStatus boatStatusDecoded = encodeDecodeBoatStatus(boatStatus);
compareBoatStatusMessages(boatStatus, boatStatusDecoded);
}
/**
* Encodes and decodes a BoatStatus, and returns it.
* @param boatStatus The BoatStatus to encode and decode.
* @return The decoded BoatStatus.
* @throws InvalidMessageException Thrown if message cannot be encoded or decoded.
*/
private static BoatStatus encodeDecodeBoatStatus(BoatStatus boatStatus) throws InvalidMessageException {
BoatStatusEncoder boatStatusEncoder = new BoatStatusEncoder();
byte[] boatStatusEncoded = boatStatusEncoder.encode(boatStatus);
BoatStatusDecoder boatStatusDecoder = new BoatStatusDecoder();
BoatStatus boatStatusDecoded = boatStatusDecoder.decode(boatStatusEncoded);
return boatStatusDecoded;
}
/**
* Compares two BoatStatus messages to check that they are equal.
* @param original The original BoatStatus message.
* @param decoded The decoded BoatStatus message.
*/
public static void compareBoatStatusMessages(BoatStatus original, BoatStatus decoded) {
Assert.assertEquals(original.getSourceID(), decoded.getSourceID());
Assert.assertEquals(original.getBoatStatus(), decoded.getBoatStatus());
Assert.assertEquals(original.getLegNumber(), decoded.getLegNumber());
Assert.assertEquals(original.getNumPenaltiesAwarded(), decoded.getNumPenaltiesAwarded());
Assert.assertEquals(original.getNumPenaltiesServed(), decoded.getNumPenaltiesServed());
Assert.assertEquals(original.getEstTimeAtNextMark(), decoded.getEstTimeAtNextMark());
Assert.assertEquals(original.getEstTimeAtFinish(), decoded.getEstTimeAtFinish());
}
}

@ -1,57 +1,79 @@
package network.MessageDecoders;
import network.MessageEncoders.RaceVisionByteEncoder;
import network.Exceptions.InvalidMessageException;
import network.MessageEncoders.CourseWindEncoder;
import network.Messages.BoatStatus;
import network.Messages.CourseWind;
import network.Messages.Enums.BoatStatusEnum;
import org.junit.Assert;
import org.junit.Test;
import java.util.ArrayList;
import shared.model.Bearing;
/**
* Created by hba56 on 23/04/17.
* Test for the CourseWind encoder and decoder
*/
public class CourseWindDecoderTest {
/**
* Creates a CourseWind message, encodes it, decodes it, and checks that the result matches the starting message.
* @throws Exception if test fails.
*/
@Test
public void getByteArrayTest(){
public void courseWindEncodeDecodeTest() throws Exception {
long time = System.currentTimeMillis();
CourseWind testCourseWind1 = new CourseWind(1, time, 2,
3, 4, 5,
7, 6);
CourseWind courseWind = new CourseWind(
1,
time,
2,
Bearing.fromDegrees(45),
4,
Bearing.fromDegrees(70),
Bearing.fromDegrees(160),
(byte) 0x13 );
long time2 = System.currentTimeMillis();
CourseWind testCourseWind2 = new CourseWind(2, time2, 2,
3, 4, 5,
7, 6);
ArrayList<CourseWind> testCourseWinds = new ArrayList<CourseWind>();
testCourseWinds.add(testCourseWind1);
testCourseWinds.add(testCourseWind2);
CourseWind courseWindDecoded = encodeDecodeCourseWind(courseWind);
compareCourseWindMessages(courseWind, courseWindDecoded);
byte[] testEncodedCourseWind = RaceVisionByteEncoder.courseWind((byte) 1, testCourseWinds);
}
CourseWindDecoder testDecoder = new CourseWindDecoder(testEncodedCourseWind);
/**
* Encodes and decodes a CourseWind, and returns it.
* @param courseWind The CourseWind to encode and decode.
* @return The decoded CourseWind.
* @throws InvalidMessageException Thrown if message cannot be encoded or decoded.
*/
private static CourseWind encodeDecodeCourseWind(CourseWind courseWind) throws InvalidMessageException {
ArrayList<CourseWind> testDecodedCourseWinds = testDecoder.getLoopMessages();
CourseWindEncoder courseWindEncoder = new CourseWindEncoder();
byte[] courseWindEncoded = courseWindEncoder.encode(courseWind);
Assert.assertEquals(testCourseWinds.get(0).getID(), testDecodedCourseWinds.get(0).getID());
Assert.assertEquals(testCourseWinds.get(0).getTime(), testDecodedCourseWinds.get(0).getTime());
Assert.assertEquals(testCourseWinds.get(0).getRaceID(), testDecodedCourseWinds.get(0).getRaceID());
Assert.assertEquals(testCourseWinds.get(0).getWindDirection(), testDecodedCourseWinds.get(0).getWindDirection());
Assert.assertEquals(testCourseWinds.get(0).getWindSpeed(), testDecodedCourseWinds.get(0).getWindSpeed());
Assert.assertEquals(testCourseWinds.get(0).getBestUpwindAngle(), testDecodedCourseWinds.get(0).getBestUpwindAngle());
Assert.assertEquals(testCourseWinds.get(0).getBestDownwindAngle(), testDecodedCourseWinds.get(0).getBestDownwindAngle());
Assert.assertEquals(testCourseWinds.get(0).getFlags(), testDecodedCourseWinds.get(0).getFlags());
CourseWindDecoder courseWindDecoder = new CourseWindDecoder();
CourseWind courseWindDecoded = courseWindDecoder.decode(courseWindEncoded);
Assert.assertEquals(testCourseWinds.get(1).getID(), testDecodedCourseWinds.get(1).getID());
Assert.assertEquals(testCourseWinds.get(1).getTime(), testDecodedCourseWinds.get(1).getTime());
Assert.assertEquals(testCourseWinds.get(1).getRaceID(), testDecodedCourseWinds.get(1).getRaceID());
Assert.assertEquals(testCourseWinds.get(1).getWindDirection(), testDecodedCourseWinds.get(1).getWindDirection());
Assert.assertEquals(testCourseWinds.get(1).getWindSpeed(), testDecodedCourseWinds.get(1).getWindSpeed());
Assert.assertEquals(testCourseWinds.get(1).getBestUpwindAngle(), testDecodedCourseWinds.get(1).getBestUpwindAngle());
Assert.assertEquals(testCourseWinds.get(1).getBestDownwindAngle(), testDecodedCourseWinds.get(1).getBestDownwindAngle());
Assert.assertEquals(testCourseWinds.get(1).getFlags(), testDecodedCourseWinds.get(1).getFlags());
return courseWindDecoded;
}
/**
* Compares two CourseWind messages to check that they are equal.
* @param original The original CourseWind message.
* @param decoded The decoded CourseWind message.
*/
public static void compareCourseWindMessages(CourseWind original, CourseWind decoded) {
Assert.assertEquals(original.getID(), decoded.getID());
Assert.assertEquals(original.getTime(), decoded.getTime());
Assert.assertEquals(original.getRaceID(), decoded.getRaceID());
Assert.assertEquals(original.getWindDirection().degrees(), decoded.getWindDirection().degrees(), 0.01);
Assert.assertEquals(original.getWindSpeedKnots(), decoded.getWindSpeedKnots(), 0.01);
Assert.assertEquals(original.getBestUpwindAngle().degrees(), decoded.getBestUpwindAngle().degrees(), 0.01);
Assert.assertEquals(original.getBestDownwindAngle().degrees(), decoded.getBestDownwindAngle().degrees(), 0.01);
Assert.assertEquals(original.getFlags(), decoded.getFlags());
}
}

@ -0,0 +1,96 @@
package network.MessageDecoders;
import network.MessageEncoders.RaceVisionByteEncoder;
import network.Messages.CourseWind;
import network.Messages.CourseWinds;
import org.junit.Test;
import shared.model.Bearing;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import static org.junit.Assert.*;
/**
* Tests for CourseWinds encoder and decoder.
*/
public class CourseWindsDecoderTest {
/**
* Tests if a CourseWinds message can be encoded and decoded correctly.
* @throws Exception Thrown if an error occurs.
*/
@Test
public void courseWindsEncodeDecodeTest() throws Exception {
long time1 = System.currentTimeMillis();
CourseWind testCourseWind1 = new CourseWind(
1,
time1,
2,
Bearing.fromDegrees(45),
4,
Bearing.fromDegrees(70),
Bearing.fromDegrees(160),
(byte) 0xCE );
long time2 = System.currentTimeMillis();
CourseWind testCourseWind2 = new CourseWind(
2,
time2,
2,
Bearing.fromDegrees(55),
4,
Bearing.fromDegrees(80),
Bearing.fromDegrees(180),
(byte) 0x0D );
List<CourseWind> testCourseWinds = new ArrayList<>();
testCourseWinds.add(testCourseWind1);
testCourseWinds.add(testCourseWind2);
CourseWinds courseWinds = new CourseWinds(CourseWinds.currentMessageVersionNumber, (byte) 2, testCourseWinds);
byte[] testEncodedCourseWind = RaceVisionByteEncoder.encode(courseWinds);
CourseWindsDecoder courseWindsDecoder = new CourseWindsDecoder();
courseWindsDecoder.decode(testEncodedCourseWind);
CourseWinds courseWindsDecoded = courseWindsDecoder.getMessage();
compareCourseWindsMessages(courseWinds, courseWindsDecoded);
}
/**
* Compares two course winds messages to ensure they are the same.
* @param original The original message.
* @param decoded The decoded message.
*/
public static void compareCourseWindsMessages(CourseWinds original, CourseWinds decoded) {
//Compare header.
assertEquals(original.getMessageVersionNumber(), decoded.getMessageVersionNumber());
assertEquals(original.getSelectedWindID(), decoded.getSelectedWindID());
assertEquals(original.getCourseWinds().size(), decoded.getCourseWinds().size());
//Compare each CourseWind.
List<CourseWind> originalWinds = original.getCourseWinds();
List<CourseWind> decodedWinds = decoded.getCourseWinds();
Iterator<CourseWind> originalIterator = originalWinds.iterator();
Iterator<CourseWind> decodedIterator = decodedWinds.iterator();
while (originalIterator.hasNext() && decodedIterator.hasNext()) {
CourseWindDecoderTest.compareCourseWindMessages(originalIterator.next(), decodedIterator.next());
}
}
}

@ -0,0 +1,79 @@
package network.MessageDecoders;
import network.Exceptions.InvalidMessageException;
import network.MessageEncoders.RaceVisionByteEncoder;
import network.Messages.BoatAction;
import network.Messages.Enums.BoatActionEnum;
import network.Messages.HeartBeat;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
/**
* Test for the HeartBeat encoder and decoder
*/
public class HeartBeatDecoderTest {
/**
* Encodes and decodes a given message.
* @param message Message to encode/decode.
* @return The decoded message.
* @throws InvalidMessageException if the message cannot be encoded.
*/
private HeartBeat encodeDecodeMessage(HeartBeat message) throws InvalidMessageException {
//Encode.
byte [] testEncodedMessage = RaceVisionByteEncoder.encode(message);
//Decode.
HeartBeatDecoder testDecoder = new HeartBeatDecoder();
testDecoder.decode(testEncodedMessage);
HeartBeat decodedMessage = testDecoder.getMessage();
return decodedMessage;
}
/**
* Tests if a heartbeat message with a given sequence number can be encoded and decoded correctly.
* @param sequenceNumber The sequenceNumber to use.
* @throws Exception if test fails.
*/
private void heartBeatTest(long sequenceNumber) throws Exception {
//Prepare message.
HeartBeat beforeMessage = new HeartBeat(sequenceNumber);
//Encode/decode it.
HeartBeat afterMessage = encodeDecodeMessage(beforeMessage);
//Compare.
assertEquals(beforeMessage.getSequenceNumber(), afterMessage.getSequenceNumber());
}
/**
* Tests if a heartbeat message with a sequence number of zero can be encoded and decoded correctly.
* @throws Exception if test fails.
*/
@Test
public void heartBeatZeroTest() throws Exception {
heartBeatTest(0);
}
/**
* Tests if a heartbeat message with a sequence number of 1234512 can be encoded and decoded correctly.
* @throws Exception if test fails.
*/
@Test
public void heartBeatNonZeroTest() throws Exception {
heartBeatTest(1234512);
}
}

@ -0,0 +1,108 @@
package network.MessageDecoders;
import network.Exceptions.InvalidMessageException;
import network.MessageEncoders.RaceVisionByteEncoder;
import network.Messages.Enums.JoinAcceptanceEnum;
import network.Messages.JoinAcceptance;
import org.junit.Test;
import static org.junit.Assert.*;
/**
* Test for the {@link network.Messages.JoinAcceptance} encoder and decoder
*/
public class JoinAcceptanceDecoderTest {
/**
* Encodes and decodes a given message.
* @param message Message to encode/decode.
* @return The decoded message.
* @throws InvalidMessageException If the message cannot be encoded.
*/
private JoinAcceptance encodeDecodeMessage(JoinAcceptance message) throws InvalidMessageException {
//Encode.
byte [] testEncodedMessage = RaceVisionByteEncoder.encode(message);
//Decode.
JoinAcceptanceDecoder testDecoder = new JoinAcceptanceDecoder();
testDecoder.decode(testEncodedMessage);
JoinAcceptance decodedMessage = testDecoder.getMessage();
return decodedMessage;
}
/**
* Tests if a specific acceptance type message can be encoded and decoded correctly.
* @param type The type of acceptance response.
* @param sourceID The source ID to assign.
* @throws Exception if test fails.
*/
private void responseTypeTest(JoinAcceptanceEnum type, int sourceID) throws Exception {
//Prepare message.
JoinAcceptance beforeMessage = new JoinAcceptance(type, sourceID);
//Encode/decode it.
JoinAcceptance afterMessage = encodeDecodeMessage(beforeMessage);
//Compare.
assertEquals(beforeMessage.getAcceptanceType().getValue(), afterMessage.getAcceptanceType().getValue());
assertEquals(beforeMessage.getSourceID(), afterMessage.getSourceID());
}
/**
* Tests if a join success message, with a source ID, can be encoded and decoded correctly.
* @throws Exception if test fails.
*/
@Test
public void joinSuccessSourceIDTest() throws Exception {
responseTypeTest(JoinAcceptanceEnum.JOIN_SUCCESSFUL, 12345);
}
/**
* Tests if a join success message, with no source ID, can be encoded and decoded correctly.
* @throws Exception if test fails.
*/
@Test
public void joinSuccessNoSourceIDTest() throws Exception {
responseTypeTest(JoinAcceptanceEnum.JOIN_SUCCESSFUL, 0);
}
/**
* Tests if a participants full message can be encoded and decoded correctly.
* @throws Exception if test fails.
*/
@Test
public void participantFullTest() throws Exception {
responseTypeTest(JoinAcceptanceEnum.RACE_PARTICIPANTS_FULL, 0);
}
/**
* Tests if a ghosts full message can be encoded and decoded correctly.
* @throws Exception if test fails.
*/
@Test
public void ghostFullTest() throws Exception {
responseTypeTest(JoinAcceptanceEnum.GHOST_PARTICIPANTS_FULL, 0);
}
/**
* Tests if a server full message can be encoded and decoded correctly.
* @throws Exception if test fails.
*/
@Test
public void serverFullTest() throws Exception {
responseTypeTest(JoinAcceptanceEnum.SERVER_FULL, 0);
}
}

@ -0,0 +1,69 @@
package network.MessageDecoders;
import network.MessageEncoders.RaceVisionByteEncoder;
import network.Messages.Enums.MarkRoundingBoatStatusEnum;
import network.Messages.Enums.MarkRoundingSideEnum;
import network.Messages.Enums.MarkRoundingTypeEnum;
import network.Messages.MarkRounding;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
/**
* Test for the MarkRounding encoder and decoder
*/
public class MarkRoundingDecoderTest {
/**
* Creates a MarkRounding message, encodes it, decodes it, and checks that the result matches the starting message.
* @throws Exception if test fails.
*/
@Test
public void markRoundingEncodeDecodeTest() throws Exception {
MarkRounding markRounding = new MarkRounding(
MarkRounding.currentMessageVersionNumber,
System.currentTimeMillis(),
567,
42,
125,
MarkRoundingBoatStatusEnum.RACING,
MarkRoundingSideEnum.PORT,
MarkRoundingTypeEnum.MARK,
(byte)45
);
byte[] encodedMessage = RaceVisionByteEncoder.encode(markRounding);
MarkRoundingDecoder markRoundingDecoder = new MarkRoundingDecoder();
markRoundingDecoder.decode(encodedMessage);
MarkRounding markRoundingDecoded = markRoundingDecoder.getMessage();
compareMarkRoundingMessages(markRounding, markRoundingDecoded);
}
/**
* Compares two MarkRounding messages to check that they are equal.
* @param original The original MarkRounding message.
* @param decoded The decoded MarkRounding message.
*/
public static void compareMarkRoundingMessages(MarkRounding original, MarkRounding decoded) {
assertEquals(original.getMessageVersionNumber(), decoded.getMessageVersionNumber());
assertEquals(original.getTime(), decoded.getTime());
assertEquals(original.getAckNum(), decoded.getAckNum());
assertEquals(original.getRaceID(), decoded.getRaceID());
assertEquals(original.getSourceID(), decoded.getSourceID());
assertEquals(original.getBoatStatus(), decoded.getBoatStatus());
assertEquals(original.getRoundingSide(), decoded.getRoundingSide());
assertEquals(original.getMarkType(), decoded.getMarkType());
assertEquals(original.getMarkID(), decoded.getMarkID());
}
}

@ -1,29 +1,62 @@
package network.MessageDecoders;
import network.MessageEncoders.RaceVisionByteEncoder;
import network.Messages.Enums.RaceStartTypeEnum;
import network.Messages.RaceStartStatus;
import org.junit.Assert;
import org.junit.Test;
/**
* Created by hba56 on 23/04/17.
* Tests for the RaceStartStatus encoder and decoder.
*/
public class RaceStartStatusDecoderTest {
/**
* Tests if a RaceStartStatus message can be encoded and decoded correctly.
* @throws Exception Thrown when an error occurs.
*/
@Test
public void getByteArrayTest(){
long time = System.currentTimeMillis();
public void raceStartStatusEncodeDecodeTest() throws Exception {
long timestamp = System.currentTimeMillis();
long time2 = System.currentTimeMillis();
byte[] encodedRaceStartStatus = RaceVisionByteEncoder.raceStartStatus(time, (short)1,
time2, 2, (char)3);
long startTime = System.currentTimeMillis() + 10 * 1000;
RaceStartStatusDecoder testDecoder = new RaceStartStatusDecoder(encodedRaceStartStatus);
RaceStartStatus raceStartStatus = new RaceStartStatus(
RaceStartStatus.currentMessageVersionNumber,
timestamp,
55,
startTime,
35,
RaceStartTypeEnum.SET_RACE_START
);
Assert.assertEquals(0b1, testDecoder.getMessageVersion());
Assert.assertEquals(time, testDecoder.getTime());
Assert.assertEquals(1, testDecoder.getAck());
Assert.assertEquals(time2, testDecoder.getStartTime());
Assert.assertEquals(2, testDecoder.getRaceID());
Assert.assertEquals((char)3, testDecoder.getNotification());
byte[] encodedRaceStartStatus = RaceVisionByteEncoder.encode(raceStartStatus);
RaceStartStatusDecoder testDecoder = new RaceStartStatusDecoder();
testDecoder.decode(encodedRaceStartStatus);
RaceStartStatus raceStartStatusDecoded = testDecoder.getMessage();
compareRaceStartStatusMessages(raceStartStatus, raceStartStatusDecoded);
}
/**
* Compares two RaceStartStatus messages to check that they are the same.
* @param original The original message.
* @param decoded The decoded message.
*/
public static void compareRaceStartStatusMessages(RaceStartStatus original, RaceStartStatus decoded) {
Assert.assertEquals(original.getMessageVersionNumber(), decoded.getMessageVersionNumber());
Assert.assertEquals(original.getTimestamp(), decoded.getTimestamp());
Assert.assertEquals(original.getAckNum(), decoded.getAckNum());
Assert.assertEquals(original.getRaceStartTime(), decoded.getRaceStartTime());
Assert.assertEquals(original.getRaceID(), decoded.getRaceID());
Assert.assertEquals(original.getNotificationType(), decoded.getNotificationType());
}
}

@ -1,27 +1,40 @@
package network.MessageDecoders;
import network.Exceptions.InvalidMessageException;
import network.MessageEncoders.RaceVisionByteEncoder;
import network.Messages.BoatStatus;
import network.Messages.Enums.BoatStatusEnum;
import network.Messages.Enums.RaceStatusEnum;
import network.Messages.Enums.RaceTypeEnum;
import network.Messages.RaceStatus;
import org.junit.Assert;
import org.junit.Test;
import shared.model.Bearing;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* Created by hba56 on 23/04/17.
* Test for the RaceStatus encoder and decoder
*/
public class RaceStatusDecoderTest {
/**
* Creates a RaceStatus message, encodes it, decodes it, and checks that the result matches the starting message.
* @throws Exception if test fails.
*/
@Test
public void getByteArrayTest(){
public void raceStatusEncodeDecodeTest() throws Exception {
long time = System.currentTimeMillis();
//Create data to serialize.
int boat1SourceID = 5;
int boat2SourceID = 8;
byte boat1Status = 2;
byte boat2Status = 2;
BoatStatusEnum boat1Status = BoatStatusEnum.RACING;
BoatStatusEnum boat2Status = BoatStatusEnum.RACING;
byte boat1LegNumber = 5;
byte boat2LegNumber = 3;
byte boat1PenaltiesAwarded = 4;
@ -33,44 +46,86 @@ public class RaceStatusDecoderTest {
long boat1TimeAtFinish = boat1TimeAtNextMark + (1000 * 15);
long boat2TimeAtFinish = boat2TimeAtNextMark + (1000 * 7);
BoatStatus boatStatus1 = new BoatStatus(boat1SourceID, boat1Status, boat1LegNumber, boat1PenaltiesAwarded, boat1PenaltiesServed, boat1TimeAtNextMark, boat1TimeAtFinish);
BoatStatus boatStatus2 = new BoatStatus(boat2SourceID, boat2Status, boat2LegNumber, boat2PenaltiesAwarded, boat2PenaltiesServed, boat2TimeAtNextMark, boat2TimeAtFinish);
BoatStatus boatStatus1 = new BoatStatus(
boat1SourceID,
boat1Status,
boat1LegNumber,
boat1PenaltiesAwarded,
boat1PenaltiesServed,
boat1TimeAtNextMark,
boat1TimeAtFinish );
BoatStatus boatStatus2 = new BoatStatus(
boat2SourceID,
boat2Status,
boat2LegNumber,
boat2PenaltiesAwarded,
boat2PenaltiesServed,
boat2TimeAtNextMark,
boat2TimeAtFinish );
int raceID = 585;
byte raceStatus = 3;
RaceStatusEnum raceStatus = RaceStatusEnum.STARTED;
long raceStartTime = time - (1000 * 31);
int windDirection = 2341;
int windSpeed = 10201;
int raceType = 1;
Bearing windDirection = Bearing.fromDegrees(185.34);
double windSpeedKnots = 14.52;
RaceTypeEnum raceType = RaceTypeEnum.MATCH_RACE;
List<BoatStatus> boatStatuses = new ArrayList<>(2);
boatStatuses.add(boatStatus1);
boatStatuses.add(boatStatus2);
RaceStatus raceStatusObject = new RaceStatus(time, raceID, raceStatus, raceStartTime, windDirection, windSpeed, raceType, boatStatuses);
RaceStatus raceStatusOriginal = new RaceStatus(
RaceStatus.currentMessageVersionNumber,
time,
raceID,
raceStatus,
raceStartTime,
windDirection,
windSpeedKnots,
raceType,
boatStatuses );
byte[] encodedRaceStatus = RaceVisionByteEncoder.encode(raceStatusOriginal);
RaceStatusDecoder decoderTest = new RaceStatusDecoder();
decoderTest.decode(encodedRaceStatus);
byte[] encodedRaceStatus = RaceVisionByteEncoder.raceStatus(raceStatusObject);
RaceStatus decodedMessage = decoderTest.getMessage();
RaceStatusDecoder decoderTest = new RaceStatusDecoder(encodedRaceStatus);
compareRaceStatusMessages(raceStatusOriginal, decodedMessage);
Assert.assertEquals(0b10, decoderTest.getVersionNum());
Assert.assertEquals(time, decoderTest.getTime());
Assert.assertEquals(raceID, decoderTest.getRace());
Assert.assertEquals(raceStatus, decoderTest.getRaceState());
Assert.assertEquals(raceStartTime, decoderTest.getStartTime());
Assert.assertEquals(windDirection, decoderTest.getRaceWindDir());
Assert.assertEquals(windSpeed, decoderTest.getRaceWindSpeed());
}
/**
* Compares two RaceStatus messages to check that they are equal.
* @param original The original RaceStatus message.
* @param decoded The decoded RaceStatus message.
*/
public static void compareRaceStatusMessages(RaceStatus original, RaceStatus decoded) {
//Compare RaceStatus body.
Assert.assertEquals(original.getMessageVersionNumber(), decoded.getMessageVersionNumber());
Assert.assertEquals(original.getCurrentTime(), decoded.getCurrentTime());
Assert.assertEquals(original.getRaceID(), decoded.getRaceID());
Assert.assertEquals(original.getRaceStatus(), decoded.getRaceStatus());
Assert.assertEquals(original.getExpectedStartTime(), decoded.getExpectedStartTime());
Assert.assertEquals(original.getWindDirection().degrees(), decoded.getWindDirection().degrees(), 0.01);
Assert.assertEquals(original.getWindSpeed(), decoded.getWindSpeed(), 0.01);
//Compare all BoatStatuses
Iterator<BoatStatus> originalIterator = original.getBoatStatuses().iterator();
Iterator<BoatStatus> decodedIterator = decoded.getBoatStatuses().iterator();
BoatStatus boat1 = decoderTest.getBoats().get(0);
while (originalIterator.hasNext() && decodedIterator.hasNext()) {
Assert.assertEquals(boat1SourceID, boat1.getSourceID());
Assert.assertEquals(boat1Status, boat1.getBoatStatus());
Assert.assertEquals(boat1LegNumber, boat1.getLegNumber());
Assert.assertEquals(boat1PenaltiesAwarded, boat1.getNumPenaltiesAwarded());
Assert.assertEquals(boat1PenaltiesServed, boat1.getNumPenaltiesServed());
Assert.assertEquals(boat1TimeAtNextMark, boat1.getEstTimeAtNextMark());
Assert.assertEquals(boat1TimeAtFinish, boat1.getEstTimeAtFinish());
BoatStatusDecoderTest.compareBoatStatusMessages(originalIterator.next(), decodedIterator.next());
}
}
}

@ -0,0 +1,89 @@
package network.MessageDecoders;
import network.Exceptions.InvalidMessageException;
import network.MessageEncoders.RaceVisionByteEncoder;
import network.Messages.Enums.RequestToJoinEnum;
import network.Messages.RequestToJoin;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.*;
/**
* Test for the RequestToJoin encoder and decoder
*/
public class RequestToJoinDecoderTest {
/**
* Encodes and decodes a given message.
* @param message Message to encode/decode.
* @return The decoded message.
* @throws InvalidMessageException If the message cannot be encoded.
*/
private RequestToJoin encodeDecodeMessage(RequestToJoin message) throws InvalidMessageException {
//Encode.
byte [] testEncodedMessage = RaceVisionByteEncoder.encode(message);
//Decode.
RequestToJoinDecoder testDecoder = new RequestToJoinDecoder();
testDecoder.decode(testEncodedMessage);
RequestToJoin decodedMessage = testDecoder.getMessage();
return decodedMessage;
}
/**
* Tests if a specific request type message can be encoded and decoded correctly.
* @param type The type of join request.
* @throws Exception if test fails.
*/
private void requestTypeTest(RequestToJoinEnum type) throws Exception {
//Prepare message.
RequestToJoin beforeMessage = new RequestToJoin(type);
//Encode/decode it.
RequestToJoin afterMessage = encodeDecodeMessage(beforeMessage);
//Compare.
assertEquals(beforeMessage.getRequestType().getValue(), afterMessage.getRequestType().getValue());
}
/**
* Tests if a spectator request message can be encoded and decoded correctly.
* @throws Exception if test fails.
*/
@Test
public void spectatorTest() throws Exception {
requestTypeTest(RequestToJoinEnum.SPECTATOR);
}
/**
* Tests if a participant request message can be encoded and decoded correctly.
* @throws Exception if test fails.
*/
@Test
public void participantTest() throws Exception {
requestTypeTest(RequestToJoinEnum.PARTICIPANT);
}
/**
* Tests if a ghost request message can be encoded and decoded correctly.
* @throws Exception if test fails.
*/
@Test
public void ghostTest() throws Exception {
requestTypeTest(RequestToJoinEnum.GHOST);
}
}

@ -1,5 +1,6 @@
package network.MessageDecoders;
import network.Exceptions.InvalidMessageException;
import network.MessageEncoders.RaceVisionByteEncoder;
import network.Messages.Enums.XMLMessageType;
import network.Messages.XMLMessage;
@ -14,13 +15,21 @@ import java.nio.charset.StandardCharsets;
import static org.junit.Assert.fail;
/**
* Created by hba56 on 20/04/17.
* Test for the XMLMessage encoder and decoder
*/
public class XMLMessageDecoderTest {
@Test
public void getByteArrayTest(){
try{
String xmlString = XMLReader.readXMLFileToString("network/raceXML/Regatta.xml", StandardCharsets.UTF_8);
/**
* Creates an XML message of the given type, using the specified filePath, encodes it, decodes it, and checks that the result matches the starting message.
* @param filePath The file path for xml file.
* @param type The type of xml file.
* @throws InvalidMessageException Thrown if message cannot be encoded.
*/
private void xmlMessageTest(String filePath, XMLMessageType type) throws InvalidMessageException {
try {
String xmlString = XMLReader.readXMLFileToString(filePath, StandardCharsets.UTF_8);
long time = System.currentTimeMillis();
@ -28,27 +37,71 @@ public class XMLMessageDecoderTest {
(byte)1,
1,
time,
XMLMessageType.REGATTA,
type,
(short)1,
xmlString );
byte[] encodedXML = RaceVisionByteEncoder.xmlMessage(message);
byte[] encodedXML = RaceVisionByteEncoder.encode(message);
XMLMessageDecoder decoderXML = new XMLMessageDecoder();
decoderXML.decode(encodedXML);
XMLMessage decodedMessage = decoderXML.getMessage();
XMLMessageDecoder decoderXML = new XMLMessageDecoder(encodedXML);
decoderXML.decode();
compareXMLMessages(message, decodedMessage);
Assert.assertEquals((byte)1, decoderXML.getMessageVersionNumber());
Assert.assertEquals((short)1, decoderXML.getAckNumber());
Assert.assertEquals(time, decoderXML.getTimeStamp());
Assert.assertEquals(XMLMessageType.REGATTA, decoderXML.getXmlMsgSubType());
Assert.assertEquals((short)1, decoderXML.getSequenceNumber());
Assert.assertEquals((short)xmlString.length(), decoderXML.getXmlMsgLength());
} catch (XMLReaderException | TransformerException e){
fail("couldn't read file" + e.getMessage());
}
}
/**
* Compares two XML messages to check that they are the same.
* @param originalMessage The first message to test.
* @param decodedMessage The second message to test.
*/
public static void compareXMLMessages(XMLMessage originalMessage, XMLMessage decodedMessage) {
Assert.assertEquals(originalMessage.getVersionNumber(), decodedMessage.getVersionNumber());
Assert.assertEquals(originalMessage.getAckNumber(), decodedMessage.getAckNumber());
Assert.assertEquals(originalMessage.getTimeStamp(), decodedMessage.getTimeStamp());
Assert.assertEquals(originalMessage.getXmlMsgSubType(), decodedMessage.getXmlMsgSubType());
Assert.assertEquals(originalMessage.getSequenceNumber(), decodedMessage.getSequenceNumber());
Assert.assertEquals(originalMessage.getXmlMsgLength(), decodedMessage.getXmlMsgLength());
Assert.assertEquals(originalMessage.getXmlMessage(), decodedMessage.getXmlMessage());
}
/**
* Tests if a regatta.xml message can be encoded and decoded.
* @throws Exception if test fails.
*/
@Test
public void regattaXMLMessageTest() throws Exception {
xmlMessageTest("network/raceXML/Regatta.xml", XMLMessageType.REGATTA);
}
/**
* Tests if a race.xml message can be encoded and decoded.
* @throws Exception if test fails.
*/
@Test
public void raceXMLMessageTest() throws Exception {
xmlMessageTest("network/raceXML/Race.xml", XMLMessageType.RACE);
}
/**
* Tests if a boat.xml message can be encoded and decoded.
* @throws Exception if test fails.
*/
@Test
public void boatXMLMessageTest() throws Exception {
xmlMessageTest("network/raceXML/Boats.xml", XMLMessageType.BOAT);
}
}

@ -2,42 +2,92 @@ package network.Utils;
import org.junit.Test;
import static network.Utils.AC35UnitConverter.convertGPS;
import static network.Utils.AC35UnitConverter.convertGPSToInt;
import static network.Utils.AC35UnitConverter.convertHeading;
import static network.Utils.AC35UnitConverter.convertTrueWindAngle;
import static network.Utils.AC35UnitConverter.*;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
/**
* Created by fwy13 on 4/05/17.
* Tests the pack/unpack and conversion functions in {@link AC35UnitConverter}.
*/
public class AC35UnitConverterTest {
/**
* Tests if gps coordinates can be unpacked.
*/
@Test
public void testUnpackGPS(){
assertTrue(unpackGPS(0) == 0);
assertTrue(unpackGPS(Integer.MAX_VALUE) == (double)Integer.MAX_VALUE * 180.0 / Math.pow(2, 31));
}
/**
* Tests if gps coodinates can be packed.
*/
@Test
public void testConvertGPS(){
assertTrue(convertGPS(0) == 0);
assertTrue(convertGPS(Integer.MAX_VALUE) == (double)Integer.MAX_VALUE * 180.0 / Math.pow(2, 31));
public void testPackGPS(){
assertTrue(packGPS(0) == 0);
assertTrue(packGPS(180) == (int)2147483648.0);
}
/**
* Tests if headings/bearings can be unpacked.
*/
@Test
public void testUnpackHeading(){
assertTrue(unpackHeading(0) == 0);
assertTrue(unpackHeading(65536) == 360.0);
}
/**
* Tests if headings/bearings can be packed.
*/
@Test
public void testConvertGPSToInt(){
assertTrue(convertGPSToInt(0) == 0);
assertTrue(convertGPSToInt(180) == (int)2147483648.0);
public void testPackHeading(){
assertTrue(packHeading(0) == 0);
assertTrue(packHeading(360) == 65536);
}
/**
* Tests if true wind angles (azimuths) can be unpacked.
*/
@Test
public void testUnpackTrueWindAngle(){
assertEquals(unpackTrueWindAngle((short)0), 0, 0.001);
assertEquals(unpackTrueWindAngle((short)32767), 180.0, 0.01);
}
/**
* Tests if true wind angles (azimuths) can be packed.
*/
@Test
public void testConvertHeading(){
assertTrue(convertHeading(0) == 0);
assertTrue(convertHeading(65536) == 360.0);
public void testPackTrueWindAngle(){
assertTrue(packTrueWindAngle(0) == (short)0);
assertTrue(packTrueWindAngle(180.0) == (short)32768);
}
/**
* Tests if millimeters per second can be unpacked to knots.
*/
@Test
public void testConvertTrueWindAngle(){
assertTrue(convertTrueWindAngle(0) == 0);
assertTrue(convertTrueWindAngle(32768) == 180.0);
public void testUnpackMMperSecToKnots(){
assertEquals(unpackMMperSecToKnots(0), 0d, 0.001);
assertEquals(unpackMMperSecToKnots(7331), 14.25, 0.01);
}
/**
* Tests if knots can be packed into millimeters per second.
*/
@Test
public void testPackKnotsToMMperSec(){
assertEquals(packKnotsToMMperSec(0), 0, 1);
assertEquals(packKnotsToMMperSec(7.44), 3828, 1);
}
}

@ -1,50 +0,0 @@
package network;
import network.MessageEncoders.RaceVisionByteEncoder;
import network.Messages.Enums.XMLMessageType;
import network.Messages.XMLMessage;
import org.junit.Assert;
import org.junit.Test;
import shared.dataInput.XMLReader;
import shared.exceptions.XMLReaderException;
import javax.xml.transform.TransformerException;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import static org.junit.Assert.fail;
/**
* Created by hba56 on 19/04/17.
*/
public class XMLMessageEncoderTest {
@Test
public void getByteArrayTest() {
try {
String xmlString = XMLReader.readXMLFileToString("network/raceXML/Regatta.xml", StandardCharsets.UTF_8);
XMLMessage message = new XMLMessage(
(byte)1,
1,
System.currentTimeMillis(),
XMLMessageType.REGATTA,
(short)1,
xmlString );
int xmlMessageLength = xmlString.getBytes().length;
byte[] encodedXML = RaceVisionByteEncoder.xmlMessage(message);
//1 + 2 + 6 + 1 + 2 + 2 + xml.byteLength
Assert.assertEquals(14 + xmlMessageLength, encodedXML.length);
} catch (XMLReaderException | TransformerException e){
fail("couldn't read file" + e.getMessage());
}
}
}

@ -0,0 +1,119 @@
<?xml version="1.0" encoding="utf-8"?>
<BoatConfig>
<Modified>2017-04-19T15:49:40+1200</Modified>
<Version>1</Version>
<Settings>
<RaceBoatType Type="AC45" />
<BoatDimension BoatLength="14.019" HullLength="13.449" />
<ZoneSize MarkZoneSize="40.347" CourseZoneSize="40.347" />
<ZoneLimits Limit1="200" Limit2="100" Limit3="40.347" Limit4="0" Limit5="-100" />
</Settings>
<BoatShapes>
<BoatShape ShapeID="0">
<Vertices>
<Vtx Seq="1" Y="0" X="-2.659" />
<Vtx Seq="2" Y="18.359" X="-2.659" />
<Vtx Seq="3" Y="18.359" X="2.659" />
<Vtx Seq="4" Y="0" X="2.659" />
</Vertices>
</BoatShape>
<BoatShape ShapeID="1">
<Vertices>
<Vtx Seq="1" Y="0" X="-1.278" />
<Vtx Seq="2" Y="8.876" X="-1.278" />
<Vtx Seq="3" Y="8.876" X="1.278" />
<Vtx Seq="4" Y="0" X="1.278" />
</Vertices>
</BoatShape>
<BoatShape ShapeID="2">
<Vertices>
<Vtx Seq="1" Y="0" X="-1.1" />
<Vtx Seq="2" Y="8.3" X="-1.1" />
<Vtx Seq="3" Y="8.3" X="1.1" />
<Vtx Seq="4" Y="0" X="1.1" />
</Vertices>
</BoatShape>
<BoatShape ShapeID="3">
<Vertices>
<Vtx Seq="1" Y="0" X="-0.75" />
<Vtx Seq="2" Y="3" X="-0.75" />
<Vtx Seq="3" Y="3" X="0.75" />
<Vtx Seq="4" Y="0" X="0.75" />
</Vertices>
</BoatShape>
<BoatShape ShapeID="4">
<Vertices>
<Vtx Seq="1" Y="0" X="-3.46" />
<Vtx Seq="2" Y="13.449" X="-3.46" />
<Vtx Seq="3" Y="14.019" X="0" />
<Vtx Seq="4" Y="13.449" X="3.46" />
<Vtx Seq="5" Y="0" X="3.46" />
</Vertices>
<Catamaran>
<Vtx Seq="1" Y="1.769" X="-2.752" />
<Vtx Seq="2" Y="0" X="-2.813" />
<Vtx Seq="3" Y="0" X="-3.34" />
<Vtx Seq="4" Y="5.351" X="-3.46" />
<Vtx Seq="5" Y="10.544" X="-3.387" />
<Vtx Seq="6" Y="13.449" X="-3.075" />
<Vtx Seq="7" Y="10.851" X="-2.793" />
<Vtx Seq="8" Y="6.669" X="-2.699" />
<Vtx Seq="9" Y="6.669" X="2.699" />
<Vtx Seq="10" Y="10.851" X="2.793" />
<Vtx Seq="11" Y="13.449" X="3.075" />
<Vtx Seq="12" Y="10.544" X="3.387" />
<Vtx Seq="13" Y="5.351" X="3.46" />
<Vtx Seq="14" Y="0" X="3.34" />
<Vtx Seq="15" Y="0" X="2.813" />
<Vtx Seq="16" Y="1.769" X="2.752" />
</Catamaran>
<Bowsprit>
<Vtx Seq="1" Y="6.669" X="-0.2" />
<Vtx Seq="2" Y="11.377" X="-0.2" />
<Vtx Seq="3" Y="14.019" X="0" />
<Vtx Seq="4" Y="11.377" X="0.2" />
<Vtx Seq="5" Y="6.669" X="0.2" />
</Bowsprit>
<Trampoline>
<Vtx Seq="1" Y="2" X="-2.699" />
<Vtx Seq="2" Y="6.438" X="-2.699" />
<Vtx Seq="3" Y="6.438" X="2.699" />
<Vtx Seq="4" Y="2" X="2.699" />
</Trampoline>
</BoatShape>
<BoatShape ShapeID="5" />
</BoatShapes>
<Boats>>
<Boat Type="Yacht" SourceID="101" ShapeID="4" HullNum="AC4501" ShortName="USA"
BoatName="ORACLE TEAM USA" Country="USA">
<GPSposition Z="1.738" Y="0.625" X="0.001" />
<MastTop Z="21.496" Y="4.233" X="0.000" />
</Boat>
<Boat Type="Yacht" SourceID="102" ShapeID="4" HullNum="AC4502" ShortName="GBR"
BoatName="Land Rover BAR" Country="United Kingdom">
<GPSposition Z="1.738" Y="0.625" X="0.001" />
<MastTop Z="21.496" Y="4.233" X="0.000" />
</Boat>
<Boat Type="Yacht" SourceID="103" ShapeID="4" HullNum="AC4503" ShortName="JPN"
BoatName="SoftBank Team Japan" Country="Japan">
<GPSposition Z="1.738" Y="0.625" X="0.001" />
<MastTop Z="21.496" Y="4.233" X="0.000" />
</Boat>
<Boat Type="Yacht" SourceID="104" ShapeID="4" HullNum="AC4504" ShortName="FRA"
BoatName="Groupama Team France" Country="France">
<GPSposition Z="1.738" Y="0.625" X="0.001" />
<MastTop Z="21.496" Y="4.233" X="0.000" />
</Boat>
<Boat Type="Yacht" SourceID="105" ShapeID="4" HullNum="AC4505" ShortName="SWE"
BoatName="Artemis Racing" Country="Sweden">
<GPSposition Z="1.738" Y="0.625" X="0.001" />
<MastTop Z="21.496" Y="4.233" X="0.000" />
</Boat>
<Boat Type="Yacht" SourceID="106" ShapeID="4" HullNum="AC4506" ShortName="NZL"
BoatName="Emirates Team New Zealand" Country="New Zealand">
<GPSposition Z="1.738" Y="0.625" X="0.001" />
<MastTop Z="21.496" Y="4.233" X="0.000" />
</Boat>
</Boats>
</BoatConfig>

@ -0,0 +1,58 @@
<?xml version="1.0" encoding="utf-8"?>
<Race>
<RaceID>17041901</RaceID>
<RaceType>Fleet</RaceType>
<CreationTimeDate>2017-04-19T15:30:00+1200</CreationTimeDate >
<RaceStartTime Time="2019-06-01T13:30:00-0400" Postpone="false" />
<Participants>
<Yacht SourceID="001" Entry="Port" />
<Yacht SourceID="002" Entry="Port" />
<Yacht SourceID="003" Entry="Port" />
<Yacht SourceID="004" Entry="Port" />
<Yacht SourceID="005" Entry="Port" />
<Yacht SourceID="006" Entry="Port" />
</Participants>
<Course>
<CompoundMark CompoundMarkID="1" Name="StartLine">
<Mark SeqID="1" Name="PRO" TargetLat="32.296577" TargetLng="-64.854304" SourceID="101" />
<Mark SeqID="2" Name="PIN" TargetLat="32.293771" TargetLng="-64.855242" SourceID="102" />
</CompoundMark>
<CompoundMark CompoundMarkID="2" Name="M1">
<Mark Name="M1" TargetLat="32.293039" TargetLng="-64.843983" SourceID="103" />
</CompoundMark>
<CompoundMark CompoundMarkID="3" Name="WindwardGate">
<Mark SeqID="1" Name="G1" TargetLat="32.284680" TargetLng="-64.850045" SourceID="104" />
<Mark SeqID="2" Name="G2" TargetLat="32.280164" TargetLng="-64.847591" SourceID="105" />
</CompoundMark>
<CompoundMark CompoundMarkID="4" Name="LeewardGate">
<Mark SeqID="1" Name="G1" TargetLat="32.309693" TargetLng="-64.835249" SourceID="106" />
<Mark SeqID="2" Name="G2" TargetLat="32.308046" TargetLng="-64.831785" SourceID="107" />
</CompoundMark>
<CompoundMark CompoundMarkID="5" Name="FinishLine">
<Mark SeqID="1" Name="PRO" TargetLat="32.317379" TargetLng="-64.839291" SourceID="108" />
<Mark SeqID="2" Name="PIN" TargetLat="32.317257" TargetLng="-64.836260" SourceID="109" />
</CompoundMark>
</Course>
<CompoundMarkSequence>
<Corner SeqID="1" CompoundMarkID="1" Rounding="SP" ZoneSize="3" />
<Corner SeqID="2" CompoundMarkID="2" Rounding="Port" ZoneSize="3" />
<Corner SeqID="3" CompoundMarkID="3" Rounding="Port" ZoneSize="3" />
<Corner SeqID="4" CompoundMarkID="4" Rounding="Port" ZoneSize="3" />
<Corner SeqID="3" CompoundMarkID="3" Rounding="Port" ZoneSize="3" />
<Corner SeqID="5" CompoundMarkID="5" Rounding="SP" ZoneSize="3"/>
</CompoundMarkSequence>
<CourseLimit>
<Limit SeqID="1" Lat="32.313922" Lon="-64.837168"/>
<Limit SeqID="2" Lat="32.317379" Lon="-64.839291"/>
<Limit SeqID="3" Lat="32.317911" Lon="-64.836996"/>
<Limit SeqID="4" Lat="32.317257" Lon="-64.836260"/>
<Limit SeqID="5" Lat="32.304273" Lon="-64.822834"/>
<Limit SeqID="6" Lat="32.279097" Lon="-64.841545"/>
<Limit SeqID="7" Lat="32.279604" Lon="-64.849871"/>
<Limit SeqID="8" Lat="32.289545" Lon="-64.854162"/>
<Limit SeqID="9" Lat="32.290198" Lon="-64.858711"/>
<Limit SeqID="10" Lat="32.297164" Lon="-64.856394"/>
<Limit SeqID="11" Lat="32.296148" Lon="-64.849184"/>
</CourseLimit>
</Race>
Loading…
Cancel
Save