Did some large tidy ups, refactoring, and documenting.

Network:
Moved the Network message classes from the Utils package to the Messages package.
Renamed BoatLocationMessage to BoatLocation and BoatStatusMessage to BoatStatus to be consistent with other message classes.
Renamed the BoatStatus enumeration to BoatStatusEnum as it conflicted with BoatStatus (the message).
Moved the BoatStatusEnum and MessageType enumerations from the Utils package to the Messages/Enums package.
Changed the BoatStatusEnum and MessageType enumerations to use map look ups in the enum.fromByte(b) method - this means that there's less copy and pasted values, and the fromByte function doesn't need to be modified if new enumerations are added.
Added a sequenceNumber member to the Heartbeat class.
Added an InvalidMessageException in the package Networking/Exceptions. This is thrown when a message is read, but it is invalid in some way.
Refactored/tidied up the Networking/BinaryMessageEncoder and Decoder classes. The decoder throws InvalidMessageExceptions instead of returning null.

Visualiser:
VisualiserInput now wraps a DataInputStream around the socket. This provides the stream.readFully(buffer) function, which is a blocking read, removing the need for busy wait loops. Replaced the getBytes() function with getNextMessage() and getNextMessageBytes(). These read the next message from the socket, and return it as a message object and a byte array, respectively.
Changed the current heartbeat timeout to 10 seconds. Added some work-in-progress code to attempt to reconnect when connection is lost. It currently doesn't work. I think Fan-Wu was doing a proper implementation, however.
VisualiserInput also has a queue of received events. Currently not really used, but could be useful in the future. Events get added as they are read.
Changed VisualiserInputs main loop to use instanceOf instead of switch+case. Feedback wanted - are there any downsides to using this instanceOf method?

#story[778,782]
main
fjc40 9 years ago
parent 466e22437b
commit b247244665

@ -4,10 +4,10 @@ package seng302;
import seng302.Networking.BinaryMessageEncoder;
import seng302.Networking.MessageEncoders.RaceVisionByteEncoder;
import seng302.Networking.MessageEncoders.XMLMessageEncoder;
import seng302.Networking.Utils.BoatLocationMessage;
import seng302.Networking.Utils.MessageType;
import seng302.Networking.Utils.RaceStatus;
import seng302.Networking.Utils.XMLMessage;
import seng302.Networking.Messages.BoatLocation;
import seng302.Networking.Messages.Enums.MessageType;
import seng302.Networking.Messages.RaceStatus;
import seng302.Networking.Messages.XMLMessage;
import java.io.*;
import java.net.*;
@ -110,12 +110,12 @@ public class MockOutput implements Runnable
*/
public synchronized void parseBoatLocation(int sourceID, double lat, double lon, double heading, double speed){
BoatLocationMessage boatLocationMessage = new BoatLocationMessage(sourceID, lat, lon, boatLocationSequenceNumber, heading, speed);
BoatLocation boatLocation = new BoatLocation(sourceID, lat, lon, boatLocationSequenceNumber, heading, speed);
//iterates the sequence number
boatLocationSequenceNumber++;
//encodeds the messages
byte[] encodedBoatLoc = RaceVisionByteEncoder.boatLocation(boatLocationMessage);
byte[] encodedBoatLoc = RaceVisionByteEncoder.boatLocation(boatLocation);
//encodeds the full message with header
BinaryMessageEncoder binaryMessageEncoder = new BinaryMessageEncoder(MessageType.BOATLOCATION, System.currentTimeMillis(), messageNumber, (short)encodedBoatLoc.length,

@ -7,13 +7,12 @@ import javafx.collections.ObservableList;
import org.geotools.referencing.GeodeticCalculator;
import org.joda.time.tz.UTCProvider;
import seng302.Constants;
import seng302.DataInput.RaceDataSource;
import seng302.MockOutput;
import seng302.Networking.Utils.BoatStatusMessage;
import seng302.Networking.Utils.Enums.BoatStatus;
import seng302.Networking.Utils.RaceStatus;
import seng302.Networking.Messages.BoatStatus;
import seng302.Networking.Messages.Enums.BoatStatusEnum;
import seng302.Networking.Messages.RaceStatus;
import java.awt.geom.Point2D;
import java.util.ArrayList;
@ -122,20 +121,20 @@ public class Race implements Runnable {
@Override
public void handle(long arg0) {
timeLeft = startTime - currentTime;
ArrayList<BoatStatusMessage> boatStatusMessages = new ArrayList<>();
ArrayList<BoatStatus> boatStatuses = new ArrayList<>();
//For each boat, we update it's position, and generate a BoatLocationMessage.
for (int i = 0; i < startingBoats.size(); i++) {
Boat boat = startingBoats.get((i + boatOffset) % startingBoats.size());
if (boat != null) {
mockOutput.parseBoatLocation(boat.getSourceID(), boat.getCurrentPosition().getLatitude(), boat.getCurrentPosition().getLongitude(), boat.getHeading(), 0);
boatStatusMessages.add(new BoatStatusMessage(boat.getSourceID(),
boat.getCurrentLeg().getLegNumber() >= 0 ? BoatStatus.RACING : BoatStatus.DNF, boat.getCurrentLeg().getLegNumber()));
boatStatuses.add(new BoatStatus(boat.getSourceID(),
boat.getCurrentLeg().getLegNumber() >= 0 ? BoatStatusEnum.RACING : BoatStatusEnum.DNF, boat.getCurrentLeg().getLegNumber()));
}
}
boatOffset = (boatOffset + 1) % (startingBoats.size());
if (timeLeft <= 60000/scaleFactor && timeLeft > 0) {
RaceStatus raceStatus = new RaceStatus(System.currentTimeMillis(), raceId, 2, startTime, 0, 2300, 1, boatStatusMessages);
RaceStatus raceStatus = new RaceStatus(System.currentTimeMillis(), raceId, 2, startTime, 0, 2300, 1, boatStatuses);
mockOutput.parseRaceStatus(raceStatus);
}
else if (timeLeft <= 0) {
@ -146,7 +145,7 @@ public class Race implements Runnable {
stop();
}
else {
RaceStatus raceStatus = new RaceStatus(System.currentTimeMillis(), raceId, 1, startTime, 0, 2300,1, boatStatusMessages);
RaceStatus raceStatus = new RaceStatus(System.currentTimeMillis(), raceId, 1, startTime, 0, 2300,1, boatStatuses);
mockOutput.parseRaceStatus(raceStatus);
}
currentTime = System.currentTimeMillis();
@ -179,7 +178,7 @@ public class Race implements Runnable {
long currentTime = System.currentTimeMillis();
//Update the total elapsed time.
totalTimeElapsed = currentTime - timeRaceStarted;
ArrayList<BoatStatusMessage> boatStatusMessages = new ArrayList<BoatStatusMessage>();
ArrayList<BoatStatus> boatStatuses = new ArrayList<BoatStatus>();
finished = 0;
//For each boat, we update it's position, and generate a BoatLocationMessage.
for (int i = 0; i < startingBoats.size(); i++) {
@ -192,15 +191,15 @@ public class Race implements Runnable {
}
if (boat.getTimeFinished() > 0) {
mockOutput.parseBoatLocation(boat.getSourceID(), boat.getCurrentPosition().getLatitude(), boat.getCurrentPosition().getLongitude(), boat.getHeading(), boat.getVelocity());
boatStatusMessages.add(new BoatStatusMessage(boat.getSourceID(), BoatStatus.FINISHED, boat.getCurrentLeg().getLegNumber()));
boatStatuses.add(new BoatStatus(boat.getSourceID(), BoatStatusEnum.FINISHED, boat.getCurrentLeg().getLegNumber()));
finished++;
} else {
mockOutput.parseBoatLocation(boat.getSourceID(), boat.getCurrentPosition().getLatitude(), boat.getCurrentPosition().getLongitude(), boat.getHeading(), boat.getVelocity());
boatStatusMessages.add(new BoatStatusMessage(boat.getSourceID(),
boat.getCurrentLeg().getLegNumber() >= 0 ? BoatStatus.RACING : BoatStatus.DNF, boat.getCurrentLeg().getLegNumber()));
boatStatuses.add(new BoatStatus(boat.getSourceID(),
boat.getCurrentLeg().getLegNumber() >= 0 ? BoatStatusEnum.RACING : BoatStatusEnum.DNF, boat.getCurrentLeg().getLegNumber()));
}
if (startingBoats.size()==finished){
RaceStatus raceStatus = new RaceStatus(System.currentTimeMillis(), raceId, 4, startTime, 0, 2300, 2, boatStatusMessages);//TODO FIX the second currentTime is a placeholder! Also, replace magic values.
RaceStatus raceStatus = new RaceStatus(System.currentTimeMillis(), raceId, 4, startTime, 0, 2300, 2, boatStatuses);//TODO FIX the second currentTime is a placeholder! Also, replace magic values.
mockOutput.parseRaceStatus(raceStatus);
}
} else {
@ -208,7 +207,7 @@ public class Race implements Runnable {
}
}
boatOffset = (boatOffset + 1) % (startingBoats.size());
RaceStatus raceStatus = new RaceStatus(System.currentTimeMillis(), raceId, 3, startTime, 0, 2300, 2, boatStatusMessages);//TODO FIX the second currentTime is a placeholder! Also, replace magic values.
RaceStatus raceStatus = new RaceStatus(System.currentTimeMillis(), raceId, 3, startTime, 0, 2300, 2, boatStatuses);//TODO FIX the second currentTime is a placeholder! Also, replace magic values.
mockOutput.parseRaceStatus(raceStatus);
}
}

@ -1,167 +1,281 @@
package seng302.Networking;
import seng302.Networking.Exceptions.InvalidMessageException;
import seng302.Networking.MessageDecoders.*;
import seng302.Networking.Messages.*;
import seng302.Networking.Utils.*;
import seng302.Networking.Messages.Enums.MessageType;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Arrays;
import java.util.zip.CRC32;
/**
* Created by hba56 on 21/04/17.
*/
/**
* This class can be used to decode/convert a byte array into a messageBody object, descended from AC35Data.
*/
public class BinaryMessageDecoder {
///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.
///The value the first sync byte should have.
private static final byte syncByte1 = (byte) 0x47;
//The value the second sync byte should have.
private static final byte syncByte2 = (byte) 0x83;
///The full message.
private byte[] fullMessage;
private byte[] header;
private byte[] message;
private byte[] crc;
///The messageHeader.
private byte[] messageHeader;
///The messageBody.
private byte[] messageBody;
///The sync bytes from the header..
private byte headerSync1;
private byte headerSync2;
///The message type from the header.
private byte headerMessageType;
private byte[] headerTimeStamp;
private byte[] headerSourceID;
private byte[] headerMessageLength;
///The timestamp from the header.
private long headerTimeStamp;
///The source ID from the header.
private int headerSourceID;
///The message body length from the header.
private int messageBodyLength;
///CRC value read from message header.
private long messageCRCValue;
///Calculated CRC value from message.
private long calculatedCRCValue;
/**
* Ctor.
* @param fullMessage Entire encoded binary message.
*/
public BinaryMessageDecoder(byte[] fullMessage) {
this.fullMessage = fullMessage;
}
public AC35Data decode() throws IndexOutOfBoundsException{
//get the header
this.header = Arrays.copyOfRange(this.fullMessage, 0, 15);
//Get the messageHeader.
this.messageHeader = Arrays.copyOfRange(this.fullMessage, 0, 15);
this.headerSync1 = this.header[0];
this.headerSync2 = this.header[1];
this.headerMessageType = this.header[2];
this.headerTimeStamp = Arrays.copyOfRange(this.header, 3, 9);
this.headerSourceID = Arrays.copyOfRange(this.header, 9, 13);
this.headerMessageLength = Arrays.copyOfRange(this.header, 13, 15);
//Get the sync bytes.
this.headerSync1 = this.messageHeader[0];
this.headerSync2 = this.messageHeader[1];
if (15 > this.fullMessage.length - 4){
//System.err.println("Message is too short.");
return null;
}
//get message
this.message = Arrays.copyOfRange(this.fullMessage, 15, this.fullMessage.length - 4);
//Get the message type.
this.headerMessageType = this.messageHeader[2];
//get crc
this.crc = Arrays.copyOfRange(this.fullMessage, this.fullMessage.length - 4, fullMessage.length);
ByteBuffer bytes = ByteBuffer.allocate(this.header.length + this.message.length);
bytes.put(this.header);
bytes.put(this.message);
//Get the header timestamp.
this.headerTimeStamp = ByteConverter.bytesToLong(Arrays.copyOfRange(this.messageHeader, 3, 9));
//Get the source ID for the message.
this.headerSourceID = ByteConverter.bytesToInt(Arrays.copyOfRange(this.messageHeader, 9, 13));
//Get the length of the message body.
this.messageBodyLength = ByteConverter.bytesToInt(Arrays.copyOfRange(this.messageHeader, 13, 15));
//Get the messageBody.
this.messageBody = Arrays.copyOfRange(this.fullMessage, this.headerLength, this.headerLength + this.messageBodyLength);
//Get the CRC value.
this.messageCRCValue = ByteConverter.bytesToLong(Arrays.copyOfRange(this.fullMessage, this.fullMessage.length - CRCLength, this.fullMessage.length));
//Combine the header and body into a single array.
ByteBuffer headerBodyByteBuffer = ByteBuffer.allocate(messageHeader.length + messageBody.length);
headerBodyByteBuffer.put(messageHeader);
headerBodyByteBuffer.put(messageBody);
//Calculate the CRC value from the header+body array.
CRC32 crc = new CRC32();
crc.reset();
crc.update(bytes.array(), 0, bytes.array().length);
//run through the checks
if (this.message.length != ByteConverter.bytesToShort(this.headerMessageLength)){//keep like this - hba65
System.err.println("message length in header does not equal the message length");
System.err.println("message length in header: " + ByteConverter.bytesToInt(this.headerMessageLength));
System.err.println("message length: " + this.message.length);
return null;
}else if(this.headerSync1 != 0x47){
System.err.println("Sync byte 1 is wrong: " + this.headerSync1);
return null;
}else if(this.headerSync2 !=(byte) 0x83){
System.err.println("Sync byte 2 is wrong: " + this.headerSync2);
return null;
}else if(crc.getValue() != ByteConverter.bytesToLong(this.crc)){
//todo check crc
System.err.println("CRC is not "+ByteConverter.bytesToLong(this.crc)+" and is instead:" + crc.getValue());
return null;
crc.update(headerBodyByteBuffer.array());
this.calculatedCRCValue = crc.getValue();
}
/**
* Decodes the byte array (binary message) this object was initialized with, and returns the corresponding message object.
* @return Message object corresponding to the binary message.
* @throws InvalidMessageException If the message cannot be decoded.
*/
public AC35Data decode() throws InvalidMessageException {
//Run through the checks to ensure that the message is valid.
if (messageBody.length != messageBodyLength) {//keep like this - hba65
//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) {
//Check the first sync byte.
throw new InvalidMessageException("Sync byte 1 is wrong. Sync byte is: " + headerSync1 + ", should be: " + syncByte1);
}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) {
//Check the CRC value.
throw new InvalidMessageException("CRC value is wrong. The calculated value is: " + calculatedCRCValue + ", should be: " + messageCRCValue);
}
MessageType mType = MessageType.valueOf(this.headerMessageType);
AC35Data data = null;
//Now we create the message object based on what is actually in the message body.
MessageType mType = MessageType.fromByte(headerMessageType);
switch(mType){
switch(mType) {
case HEARTBEAT:
// System.out.println("HeartBeat Message!");
data = new Heartbeat();
break;
//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(ByteConverter.bytesToLong(messageBody));
case RACESTATUS:
// System.out.println("Race Status Message");
RaceStatusDecoder rsdecoder = new RaceStatusDecoder(this.message);
data = new RaceStatus(rsdecoder.getTime(), rsdecoder.getRace(), rsdecoder.getRaceState(), rsdecoder.getStartTime(), rsdecoder.getRaceWindDir(), rsdecoder.getRaceWindSpeed(), rsdecoder.getRaceType(), rsdecoder.getBoats());
break;
//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.
break;
//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(this.message);
//System.out.println("XML Message!");
XMLMessageDecoder xmdecoder = new XMLMessageDecoder(messageBody);
xmdecoder.decode();
data = new XMLMessage(xmdecoder.getAckNumber(), xmdecoder.getTimeStamp(), xmdecoder.getXmlMsgSubType(), xmdecoder.getSequenceNumber(), xmdecoder.getXmlMsgLength(), xmdecoder.getXmlMessageInputStream());
break;
return new XMLMessage(xmdecoder.getAckNumber(), xmdecoder.getTimeStamp(), xmdecoder.getXmlMsgSubType(), xmdecoder.getSequenceNumber(), xmdecoder.getXmlMsgLength(), xmdecoder.getXmlMessageInputStream());
case RACESTARTSTATUS:
// System.out.println("Race Start Status Message");
RaceStartStatusDecoder rssDecoder = new RaceStartStatusDecoder(this.message);
data = new RaceStartStatus(rssDecoder.getTime(), rssDecoder.getAck(), rssDecoder.getStartTime(), rssDecoder.getRaceID(), rssDecoder. getNotification());
break;
//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
break;
//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
break;
//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
break;
//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(this.message);
data = blDecoder.getMessage();
break;
//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(this.message);
data = mrDecoder.getMarkRounding();
break;
//System.out.println("Mark Rounding Message!");
MarkRoundingDecoder mrDecoder = new MarkRoundingDecoder(messageBody);
return mrDecoder.getMarkRounding();
case COURSEWIND:
// System.out.println("Couse Wind Message!");
CourseWindDecoder cwDecoder = new CourseWindDecoder(this.message);
data =new CourseWinds(cwDecoder.getMessageVersionNumber(), cwDecoder.getByteWindID(), cwDecoder.getLoopMessages());
break;
//System.out.println("Couse 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(this.message);
data = awDecoder.getAverageWind();
break;
//System.out.println("Average Wind Message!");
AverageWindDecoder awDecoder = new AverageWindDecoder(messageBody);
return awDecoder.getAverageWind();
default:
// System.out.println("Broken Message!");
break;
//System.out.println("Broken Message!");
throw new InvalidMessageException("Broken message! Did not recognise message type: " + headerMessageType + ".");
}
return data;
}
public long getTimeStamp() {
return ByteConverter.bytesToLong(this.headerTimeStamp);
/**
* Returns the first sync byte value.
* @return The first sync byte value.
*/
public byte getHeaderSync1() {
return headerSync1;
}
/**
* Returns the second sync byte value.
* @return The second sync byte value.
*/
public byte getHeaderSync2() {
return headerSync2;
}
/**
* Returns the message type.
* @return The message type.
*/
public byte getHeaderMessageType() {
return headerMessageType;
}
/**
* Returns the header timestamp.
* @return The header timestamp.
*/
public long getHeaderTimeStamp() {
return headerTimeStamp;
}
public int getSourceID() {
return ByteConverter.bytesToInt(this.headerSourceID, ByteOrder.BIG_ENDIAN);
/**
* Returns the header source ID.
* @return The header source ID.
*/
public int getHeaderSourceID() {
return headerSourceID;
}
public short getMessageLength() {
return ByteConverter.bytesToShort(this.headerMessageLength);
/**
* Returns the message body length, according to the header.
* @return The message body length.
*/
public int getMessageBodyLength() {
return messageBodyLength;
}
public int getMessageType(){
return (int) this.headerMessageType;
/**
* Returns the message CRC value, according to the header.
* @return The message CRC value.
*/
public long getMessageCRCValue() {
return messageCRCValue;
}
public byte[] getMessage() {
return message;
/**
* Returns the calculated CRC value from the message header + body contents.
* @return The calculated CRC value.
*/
public long getCalculatedCRCValue() {
return calculatedCRCValue;
}
/**
* Returns the message body.
* @return The message body.
*/
public byte[] getMessageBody() {
return messageBody;
}
}

@ -1,10 +1,9 @@
package seng302.Networking;
import seng302.Networking.Utils.MessageType;
import seng302.Networking.Messages.Enums.MessageType;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.zip.CRC32;
import static seng302.Networking.Utils.ByteConverter.intToBytes;
@ -14,56 +13,96 @@ import static seng302.Networking.Utils.ByteConverter.shortToBytes;
/**
* Created by hba56 on 21/04/17.
*/
/**
* This class can be used to encode/convert a byte array message body, plus header data into a byte array containing the entire message, ready to send.
*/
public class BinaryMessageEncoder {
///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.
///The full message.
private byte[] fullMessage;
private byte[] header;
private byte[] message;
//private byte[] crc;
///The message header.
private byte[] messageHeader;
///The message body.
private byte[] messageBody;
///First sync byte value.
private byte headerSync1 = (byte)0x47;
///Second sync byte value.
private byte headerSync2 = (byte)0x83;
///The message type to place in header.
private byte headerMessageType;
///The timestamp to place in header.
private long headerTimeStamp;
///The source ID to place in header.
private int headerSourceID;
private short headerMessageLength;
///The message length to place in header.
private short bodyMessageLength;
public BinaryMessageEncoder(MessageType headerMessageType, long headerTimeStamp, int headerSourceID, short headerMessageLength, byte[] message){
//set the header
///The calculated CRC value.
private long calculatedCRCValue;
/**
* Ctor. Constructs a encoder and encodes the full message. Retrieve it with encoder.getFullMessage().
* @param headerMessageType The message type to send.
* @param headerTimeStamp Timestamp of the message.
* @param headerSourceID Source ID of the message.
* @param bodyMessageLength The length of the body of the message.
* @param messageBody The body of the message (that is, the payload).
*/
public BinaryMessageEncoder(MessageType headerMessageType, long headerTimeStamp, int headerSourceID, short bodyMessageLength, byte[] messageBody) {
//Set the header parameters.
this.headerMessageType = headerMessageType.getValue();
this.headerTimeStamp = headerTimeStamp;
this.headerSourceID = headerSourceID;
this.headerMessageLength = headerMessageLength;
this.bodyMessageLength = bodyMessageLength;
ByteBuffer tempHeaderByteBuffer = ByteBuffer.allocate(15);
//Place the header parameters into a buffer.
ByteBuffer tempHeaderByteBuffer = ByteBuffer.allocate(this.headerLength);
tempHeaderByteBuffer.put(this.headerSync1);
tempHeaderByteBuffer.put(this.headerSync2);
tempHeaderByteBuffer.put(this.headerMessageType);
tempHeaderByteBuffer.put(longToBytes(this.headerTimeStamp, 6));
tempHeaderByteBuffer.putInt(this.headerSourceID);
tempHeaderByteBuffer.put(shortToBytes(this.headerMessageLength));
tempHeaderByteBuffer.put(intToBytes(this.headerSourceID));
tempHeaderByteBuffer.put(shortToBytes(this.bodyMessageLength));
this.messageHeader = tempHeaderByteBuffer.array();
this.header = tempHeaderByteBuffer.array();
//Set the message body.
this.messageBody = messageBody;
//set the message
this.message = message;
//set full message
ByteBuffer tempMessageByteBuffer = ByteBuffer.allocate(19+this.headerMessageLength);
tempMessageByteBuffer.put(this.header);
tempMessageByteBuffer.put(this.message);
//Place header and body into a buffer.
ByteBuffer tempHeaderBodyByteBuffer = ByteBuffer.allocate(this.messageHeader.length + this.bodyMessageLength);
tempHeaderBodyByteBuffer.put(this.messageHeader);
tempHeaderBodyByteBuffer.put(this.messageBody);
//Calculate the CRC from header + body.
CRC32 crc = new CRC32();
crc.reset();
byte[] messageAndHeader = new byte[this.header.length + this.message.length];
System.arraycopy(this.header, 0, messageAndHeader, 0, this.header.length);
System.arraycopy(this.message, 0, messageAndHeader, this.header.length, this.message.length);
crc.update(messageAndHeader);
crc.update(tempHeaderBodyByteBuffer.array());
this.calculatedCRCValue = crc.getValue();
//Place header, body, and CRC value in buffer.
ByteBuffer tempFullMessageByteBuffer = ByteBuffer.allocate(this.messageHeader.length + this.messageBody.length + this.CRCLength);
tempFullMessageByteBuffer.put(this.messageHeader);
tempFullMessageByteBuffer.put(this.messageBody);
tempFullMessageByteBuffer.put(intToBytes((int) this.calculatedCRCValue));
tempMessageByteBuffer.put(intToBytes((int) crc.getValue()));
this.fullMessage = tempMessageByteBuffer.array();
//Set the full message.
this.fullMessage = tempFullMessageByteBuffer.array();
}
/**
* Returns the full encoded message. This includes the header, body, and CRC.
* @return Full encoded message.
*/
public byte[] getFullMessage() {
return fullMessage;
}

@ -0,0 +1,29 @@
package seng302.Networking.Exceptions;
/**
* Created by f123 on 07-May-17.
*/
/**
* Exception which is thrown when a message is read, but it is invalid in some way (CRC is wrong, sync bytes, etc...).
*/
public class InvalidMessageException extends Exception
{
/**
* Ctor.
* @param message String message.
*/
public InvalidMessageException(String message) {
super(message);
}
/**
* Ctor.
* @param message String message.
* @param cause Cause of the exception.
*/
public InvalidMessageException(String message, Throwable cause) {
super(message, cause);
}
}

@ -1,6 +1,6 @@
package seng302.Networking.MessageDecoders;
import seng302.Networking.Utils.AverageWind;
import seng302.Networking.Messages.AverageWind;
import seng302.Networking.Utils.ByteConverter;
import java.util.Arrays;

@ -1,9 +1,7 @@
package seng302.Networking.MessageDecoders;
import seng302.Networking.Utils.BoatLocationMessage;
import seng302.Networking.Messages.BoatLocation;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Arrays;
import static seng302.Networking.Utils.ByteConverter.*;
@ -35,7 +33,7 @@ public class BoatLocationDecoder {
private byte[] currentSet;
private byte[] rudderAngle;
private BoatLocationMessage message;
private BoatLocation message;
public BoatLocationDecoder(byte[] encodedBoatLocation) {
messageVersionNumber = encodedBoatLocation[0];
@ -64,7 +62,7 @@ public class BoatLocationDecoder {
// System.out.println(bytesToInt(sourceID));
// System.out.println(bytesToInt(boatSpeed));
message = new BoatLocationMessage(messageVersionNumber, bytesToLong(time),
message = new BoatLocation(messageVersionNumber, bytesToLong(time),
bytesToInt(sourceID), bytesToInt(seqNum),
deviceType, bytesToInt(latitude),
bytesToInt(longitude), bytesToInt(altitude),
@ -79,7 +77,7 @@ public class BoatLocationDecoder {
}
public BoatLocationMessage getMessage() {
public BoatLocation getMessage() {
return message;
}
}

@ -1,9 +1,7 @@
package seng302.Networking.MessageDecoders;
import seng302.Networking.Utils.CourseWind;
import seng302.Networking.Messages.CourseWind;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.Arrays;

@ -1,7 +1,7 @@
package seng302.Networking.MessageDecoders;
import seng302.Networking.Utils.ByteConverter;
import seng302.Networking.Utils.MarkRounding;
import seng302.Networking.Messages.MarkRounding;
import java.util.Arrays;

@ -1,6 +1,6 @@
package seng302.Networking.MessageDecoders;
import seng302.Networking.Utils.BoatStatusMessage;
import seng302.Networking.Messages.BoatStatus;
import java.util.ArrayList;
import java.util.Arrays;
@ -30,7 +30,7 @@ public class RaceStatusDecoder {
private short raceWindSpeed;
private int numberOfBoats;
private int raceType;
private ArrayList<BoatStatusMessage> boats = new ArrayList<>();
private ArrayList<BoatStatus> boats = new ArrayList<>();
public RaceStatusDecoder(byte[] encodedRaceStatus){
@ -66,7 +66,7 @@ public class RaceStatusDecoder {
byte[] estTimeAtNextMark = Arrays.copyOfRange(boatBytes, 8, 14);
byte[] estTimeAtFinish = Arrays.copyOfRange(boatBytes, 14, 20);
BoatStatusMessage boat = new BoatStatusMessage(bytesToInt(sourceID),boatStatus,
BoatStatus boat = new BoatStatus(bytesToInt(sourceID),boatStatus,
legNumber, numPenaltiesAwarded, numPenaltiesServed,
bytesToLong(estTimeAtNextMark), bytesToLong(estTimeAtFinish));
@ -111,7 +111,7 @@ public class RaceStatusDecoder {
return raceType;
}
public ArrayList<BoatStatusMessage> getBoats() {
public ArrayList<BoatStatus> getBoats() {
return boats;
}
}

@ -1,7 +1,7 @@
package seng302.Networking.MessageEncoders;
import seng302.Networking.Utils.*;
import seng302.Networking.Messages.*;
import java.nio.ByteBuffer;
import java.util.ArrayList;
@ -32,9 +32,9 @@ public class RaceVisionByteEncoder {
*/
public static byte[] raceStatus(RaceStatus raceStatus){
ArrayList<BoatStatusMessage> boatStatusMessages = raceStatus.getBoatStatusMessages();
ArrayList<BoatStatus> boatStatuses = raceStatus.getBoatStatuses();
ByteBuffer raceStatusMessage = ByteBuffer.allocate(24 + 20* boatStatusMessages.size());
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)
@ -43,7 +43,7 @@ public class RaceVisionByteEncoder {
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(boatStatusMessages.size(), 1);
byte[] numBoats = intToBytes(boatStatuses.size(), 1);
byte[] bytesRaceType = intToBytes(raceStatus.getRaceType(), 1);//1 match race, 2 fleet race
raceStatusMessage.put(versionNum);
@ -56,14 +56,14 @@ public class RaceVisionByteEncoder {
raceStatusMessage.put(numBoats);
raceStatusMessage.put(bytesRaceType);
for (int i = 0; i < boatStatusMessages.size(); i++){
byte[] sourceID = intToBytes(boatStatusMessages.get(i).getSourceID());
byte[] boatStatus = intToBytes(boatStatusMessages.get(i).getBoatStatus(), 1);
byte[] legNum = intToBytes(boatStatusMessages.get(i).getLegNumber(), 1);
byte[] numPenalties = intToBytes(boatStatusMessages.get(i).getNumPenaltiesAwarded(), 1);
byte[] numPenaltiesServed = intToBytes(boatStatusMessages.get(i).getNumPenaltiesServed(), 1);
byte[] estNextMarkTime = longToBytes(boatStatusMessages.get(i).getEstTimeAtNextMark(), 6);
byte[] estFinishTime = longToBytes( boatStatusMessages.get(i).getEstTimeAtFinish(), 6);
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);
@ -173,29 +173,29 @@ public class RaceVisionByteEncoder {
return result.array();
}
public static byte[] boatLocation(BoatLocationMessage boatLocationMessage){
public static byte[] boatLocation(BoatLocation boatLocation){
int messageVersionNumber = 0b1;
byte[] time = longToBytes(boatLocationMessage.getTime(), 6);
byte[] sourceID = intToBytes(boatLocationMessage.getSourceID(), 4);
byte[] seqNum = longToBytes(boatLocationMessage.getSequenceNumber(), 4);
byte[] deviceType = intToBytes(boatLocationMessage.getDeviceType(), 1);
byte[] latitude = intToBytes(boatLocationMessage.getLatitude(), 4);
byte[] longitude = intToBytes(boatLocationMessage.getLongitude(), 4);
byte[] altitude = intToBytes(boatLocationMessage.getAltitude(), 4);
byte[] heading = intToBytes(boatLocationMessage.getHeading(), 2);
byte[] pitch = intToBytes(boatLocationMessage.getPitch(), 2);
byte[] roll = intToBytes(boatLocationMessage.getRoll(), 2);
byte[] boatSpeed = intToBytes(boatLocationMessage.getBoatSpeed(), 2);
byte[] cog = intToBytes(boatLocationMessage.getBoatCOG(), 2);
byte[] sog = intToBytes(boatLocationMessage.getBoatSOG(), 2);
byte[] apparentWindSpeed = intToBytes(boatLocationMessage.getApparentWindSpeed(), 2);
byte[] apparentWindAngle = intToBytes(boatLocationMessage.getApparentWindAngle(), 2);
byte[] trueWindSpeed = intToBytes(boatLocationMessage.getTrueWindSpeed(), 2);
byte[] trueWindDirection = intToBytes(boatLocationMessage.getTrueWindDirection(), 2);
byte[] trueWindAngle = intToBytes(boatLocationMessage.getTrueWindAngle(), 2);
byte[] currentDrift = intToBytes(boatLocationMessage.getCurrentDrift(), 2);
byte[] currentSet = intToBytes(boatLocationMessage.getCurrentSet(), 2);
byte[] rudderAngle = intToBytes(boatLocationMessage.getRudderAngle(), 2);
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));

@ -0,0 +1,35 @@
package seng302.Networking.Messages;
/**
* Created by fwy13 on 25/04/17.
*/
import seng302.Networking.Messages.Enums.MessageType;
/**
* The base class for all message types.
*/
public abstract class AC35Data {
///Message type from the header.
private MessageType type;
/**
* Ctor.
* @param type The concrete type of this message.
*/
public AC35Data (MessageType type){
this.type = type;
}
/**
* The concrete type of message this is.
* @return The type of message this is.
*/
public MessageType getType() {
return type;
}
}

@ -1,9 +1,11 @@
package seng302.Networking.Utils;
package seng302.Networking.Messages;
import seng302.Networking.Messages.Enums.MessageType;
/**
* Created by fwy13 on 25/04/17.
*/
public class AverageWind extends AC35Data{
public class AverageWind extends AC35Data {
private int msgNum;
private long lngTime;

@ -1,10 +1,12 @@
package seng302.Networking.Utils;
package seng302.Networking.Messages;
/**
* Created by f123 on 21-Apr-17.
*/
import SharedModel.Constants;
import seng302.Networking.Utils.AC35UnitConverter;
import seng302.Networking.Messages.Enums.MessageType;
import static seng302.Networking.Utils.AC35UnitConverter.convertGPS;
import static seng302.Networking.Utils.AC35UnitConverter.convertGPSToInt;
@ -12,8 +14,8 @@ import static seng302.Networking.Utils.AC35UnitConverter.convertGPSToInt;
/**
* Represents the information in a boat location message (AC streaming spec: 4.9).
*/
public class BoatLocationMessage extends AC35Data
{
public class BoatLocation extends AC35Data {
public static final byte Unknown = 0;
public static final byte RacingYacht = 1;
public static final byte CommitteeBoat = 2;
@ -93,7 +95,7 @@ public class BoatLocationMessage extends AC35Data
/**
* Ctor. Default.
*/
public BoatLocationMessage() {
public BoatLocation() {
super(MessageType.BOATLOCATION);
}
@ -123,7 +125,7 @@ public class BoatLocationMessage extends AC35Data
* @param currentSet current set
* @param rudderAngle rudder angle
*/
public BoatLocationMessage(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, 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) {
super(MessageType.BOATLOCATION);
this.messageVersionNumber = messageVersionNumber;
@ -150,7 +152,7 @@ public class BoatLocationMessage extends AC35Data
this.rudderAngle = rudderAngle;
}
public BoatLocationMessage(int sourceID, double lat, double lon, long sequenceNumber, double heading, double boatSpeed) {
public BoatLocation(int sourceID, double lat, double lon, long sequenceNumber, double heading, double boatSpeed) {
super(MessageType.BOATLOCATION);
this.messageVersionNumber = (byte) 1;

@ -1,11 +1,13 @@
package seng302.Networking.Utils;
package seng302.Networking.Messages;
import seng302.Networking.Utils.Enums.BoatStatus;
import seng302.Networking.Utils.ByteConverter;
import seng302.Networking.Messages.Enums.BoatStatusEnum;
/**
* Created by hba56 on 23/04/17.
*/
public class BoatStatusMessage {
public class BoatStatus {
private int sourceID;
private byte boatStatus;
private byte legNumber;
@ -14,7 +16,7 @@ public class BoatStatusMessage {
private long estTimeAtNextMark;
private long estTimeAtFinish;
public BoatStatusMessage(int sourceID, byte boatStatus, byte legNumber, byte numPenaltiesAwarded, byte numPenaltiesServed, long estTimeAtNextMark, long estTimeAtFinish) {
public BoatStatus(int sourceID, byte boatStatus, byte legNumber, byte numPenaltiesAwarded, byte numPenaltiesServed, long estTimeAtNextMark, long estTimeAtFinish) {
this.sourceID = sourceID;
this.boatStatus = boatStatus;
this.legNumber = legNumber;
@ -25,9 +27,9 @@ public class BoatStatusMessage {
}
public BoatStatusMessage(int sourceID, BoatStatus boatStatus, int legNum) {
public BoatStatus(int sourceID, BoatStatusEnum boatStatusEnum, int legNum) {
this.sourceID = sourceID;
this.boatStatus = boatStatus.getValue();
this.boatStatus = boatStatusEnum.getValue();
this.legNumber = ByteConverter.intToBytes(legNum)[0];
numPenaltiesAwarded = 0;
numPenaltiesServed = 0;

@ -1,9 +1,11 @@
package seng302.Networking.Utils;
package seng302.Networking.Messages;
import seng302.Networking.Messages.Enums.MessageType;
/**
* Created by fwy13 on 21/04/17.
*/
public class CourseWind extends AC35Data{
public class CourseWind extends AC35Data {
private int ID, raceID, windDirection, windSpeed, bestUpwindAngle, bestDownwindAngle, flags;
private long time;

@ -1,11 +1,13 @@
package seng302.Networking.Utils;
package seng302.Networking.Messages;
import seng302.Networking.Messages.Enums.MessageType;
import java.util.ArrayList;
/**
* Created by fwy13 on 25/04/17.
*/
public class CourseWinds extends AC35Data{
public class CourseWinds extends AC35Data {
private int msgVerNum;
private int selectedWindID;

@ -0,0 +1,79 @@
package seng302.Networking.Messages.Enums;
import java.util.HashMap;
import java.util.Map;
/**
* Created by esa46 on 28/04/17.
*/
/**
* Enumeration that encapsulates the various statuses a boat can have.
*/
public enum BoatStatusEnum {
UNDEFINED(0),
PRESTART(1),
RACING(2),
FINISHED(3),
DNS(4),
DNF(5),
DSQ(6),
OCS(7),
NOT_A_STATUS(-1);
///Primitive value of the enum.
private byte value;
/**
* Ctor. Creates a BoatStatusEnum from a given primitive integer value, cast to a byte.
* @param value Integer, which is cast to byte, to construct from.
*/
private BoatStatusEnum(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 BoatStatusEnum values.
private static final Map<Byte, BoatStatusEnum> byteToStatusMap = new HashMap<>();
/**
* Static initialization block. Initializes the byteToStatusMap.
*/
static {
for (BoatStatusEnum type : BoatStatusEnum.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 BoatStatusEnum value.
* @return The BoatStatusEnum value which corresponds to the given byte value.
*/
public static BoatStatusEnum fromByte(byte boatStatusByte) {
//Gets the corresponding MessageType from the map.
BoatStatusEnum type = byteToStatusMap.get(boatStatusByte);
if (type == null) {
//If the byte value wasn't found, return the NOT_A_STATUS BoatStatusEnum.
return BoatStatusEnum.NOT_A_STATUS;
}
else {
//Otherwise, return the BoatStatusEnum.
return type;
}
}
}

@ -0,0 +1,83 @@
package seng302.Networking.Messages.Enums;
/**
* Created by hba56 on 21/04/17.
*/
import java.util.HashMap;
import java.util.Map;
/**
* Enumeration that encapsulates the various types of messages that can be sent.
*/
public enum MessageType {
HEARTBEAT(1),
RACESTATUS(12),
DISPLAYTEXTMESSAGE(20),
XMLMESSAGE(26),
RACESTARTSTATUS(27),
YACHTEVENTCODE(29),
YACHTACTIONCODE(31),
CHATTERTEXT(36),
BOATLOCATION(37),
MARKROUNDING(38),
COURSEWIND(44),
AVGWIND(47),
NOTAMESSAGE(0);
///Primitive value of the enum.
private byte value;
/**
* Ctor. 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) {
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 MessageType values.
private static final Map<Byte, MessageType> byteToTypeMap = new HashMap<>();
/**
* Static initialization block. Initializes the byteToTypeMap.
*/
static {
for (MessageType type : MessageType.values()) {
byteToTypeMap.put(type.value, type);
}
}
/**
* Returns the enumeration value which corresponds to a given byte value.
* @param messageTypeByte Byte value to convert to a MessageType value.
* @return The MessageType value which corresponds to the given byte value.
*/
public static MessageType fromByte(byte messageTypeByte) {
//Gets the corresponding MessageType from the map.
MessageType type = byteToTypeMap.get(messageTypeByte);
if (type == null) {
//If the byte value wasn't found, return the NOTAMESSAGE MessageType.
return MessageType.NOTAMESSAGE;
}
else {
//Otherwise, return the MessageType.
return type;
}
}
}

@ -0,0 +1,33 @@
package seng302.Networking.Messages;
import seng302.Networking.Messages.Enums.MessageType;
/**
* Created by fwy13 on 25/04/17.
*/
/**
* Represents a Heartbeat message.
*/
public class Heartbeat extends AC35Data {
///Sequence number of the heartbeat.
private long sequenceNumber;
/**
* Ctor.
* @param sequenceNumber Sequence number of the heartbeat.
*/
public Heartbeat(long sequenceNumber) {
super(MessageType.HEARTBEAT);
this.sequenceNumber = sequenceNumber;
}
/**
* Returns the sequence number of this heartbeat message.
* @return Sequence number of this heartbeat message.
*/
public long getSequenceNumber() {
return sequenceNumber;
}
}

@ -1,9 +1,11 @@
package seng302.Networking.Utils;
package seng302.Networking.Messages;
import seng302.Networking.Messages.Enums.MessageType;
/**
* Created by fwy13 on 25/04/17.
*/
public class MarkRounding extends AC35Data{
public class MarkRounding extends AC35Data {
private int msgVerNum;
private long time;
@ -49,7 +51,19 @@ public class MarkRounding extends AC35Data{
this.markID = markID;
}
/**
* Returns the boat (source) ID for this message.
* @return Boat ID for this message.
*/
public int getSourceID() {
return sourceID;
}
/**
* Returns the timestamp for this message.
* @return Timestamp for this message.
*/
public long getTime() {
return time;
}
}

@ -1,4 +1,6 @@
package seng302.Networking.Utils;
package seng302.Networking.Messages;
import seng302.Networking.Messages.Enums.MessageType;
/**
* Created by fwy13 on 19/04/17.

@ -1,11 +1,12 @@
package seng302.Networking.Utils;
package seng302.Networking.Messages;
import java.util.ArrayList;
import seng302.Networking.Messages.Enums.MessageType;
/**
* Created by fwy13 on 25/04/17.
*/
public class RaceStartStatus extends AC35Data{
public class RaceStartStatus extends AC35Data {
private long timestamp;
private int ackNum;
private long raceStartTime;

@ -1,11 +1,14 @@
package seng302.Networking.Utils;
package seng302.Networking.Messages;
import seng302.Networking.Messages.Enums.MessageType;
import java.util.ArrayList;
/**
* Created by fwy13 on 25/04/17.
*/
public class RaceStatus extends AC35Data{
public class RaceStatus extends AC35Data {
private long currentTime;
private int raceID;
private int raceStatus;
@ -13,9 +16,9 @@ public class RaceStatus extends AC35Data{
private int windDirection;
private int windSpeed;
private int raceType;
private ArrayList<BoatStatusMessage> boatStatusMessages;
private ArrayList<BoatStatus> boatStatuses;
public RaceStatus(long currentTime, int raceID, int raceStatus, long expectedStartTime, int windDirection, int windSpeed, int raceType, ArrayList<BoatStatusMessage> boatStatusMessages){
public RaceStatus(long currentTime, int raceID, int raceStatus, long expectedStartTime, int windDirection, int windSpeed, int raceType, ArrayList<BoatStatus> boatStatuses){
super(MessageType.RACESTATUS);
this.currentTime = currentTime;
this.raceID = raceID;
@ -24,7 +27,7 @@ public class RaceStatus extends AC35Data{
this.windDirection = windDirection;
this.windSpeed = windSpeed;
this.raceType = raceType;
this.boatStatusMessages = boatStatusMessages;//note this is a copy so any alterations to the parent will affect this.
this.boatStatuses = boatStatuses;//note this is a copy so any alterations to the parent will affect this.
}
@ -70,9 +73,9 @@ public class RaceStatus extends AC35Data{
return raceType;
}
public ArrayList<BoatStatusMessage> getBoatStatusMessages()
public ArrayList<BoatStatus> getBoatStatuses()
{
return boatStatusMessages;
return boatStatuses;
}
public boolean isNotActive() {

@ -1,13 +1,13 @@
package seng302.Networking.Utils;
package seng302.Networking.Messages;
import org.xml.sax.InputSource;
import seng302.Networking.Messages.Enums.MessageType;
import java.io.InputStream;
/**
* Created by fwy13 on 25/04/17.
*/
public class XMLMessage extends AC35Data{
public class XMLMessage extends AC35Data {
private int ackNumber;
private long timeStamp;

@ -1,6 +1,8 @@
package seng302.Networking.PacketDump;
import seng302.Networking.BinaryMessageDecoder;
import seng302.Networking.Exceptions.InvalidMessageException;
import seng302.Networking.Messages.AC35Data;
import java.io.IOException;
import java.net.URISyntaxException;
@ -48,7 +50,13 @@ public class AC35DumpReader {
}
for (AC35Packet pack: packets){
BinaryMessageDecoder decoder = new BinaryMessageDecoder(pack.getData());
decoder.decode();
try {
AC35Data data = decoder.decode();
}
catch (InvalidMessageException e) {
System.out.println(e.getMessage());
}
}
}

@ -1,18 +0,0 @@
package seng302.Networking.Utils;
/**
* Created by fwy13 on 25/04/17.
*/
public abstract class AC35Data {
protected MessageType type;
public AC35Data (MessageType type){
this.type = type;
}
public MessageType getType() {
return type;
}
}

@ -1,41 +0,0 @@
package seng302.Networking.Utils.Enums;
/**
* Created by esa46 on 28/04/17.
*/
public enum BoatStatus {
UNDEFINED(0), PRESTART(1), RACING(2), FINISHED(3), DNS(4), DNF(5), DSQ(6), OCS(7), NOT_A_STATUS(-1);
private byte value;
private BoatStatus(int value) { this.value = (byte)value; }
public byte getValue() {
return value;
}
public static BoatStatus valueOf(byte bite){
switch(bite){
case 0:
return UNDEFINED;
case 1:
return PRESTART;
case 2:
return RACING;
case 3:
return FINISHED;
case 4:
return DNS;
case 5:
return DNF;
case 6:
return DSQ;
case 7:
return OCS;
default:
return NOT_A_STATUS;
}
}
}

@ -1,12 +0,0 @@
package seng302.Networking.Utils;
/**
* Created by fwy13 on 25/04/17.
*/
public class Heartbeat extends AC35Data{
public Heartbeat(){
super(MessageType.HEARTBEAT);
}
}

@ -1,49 +0,0 @@
package seng302.Networking.Utils;
/**
* Created by hba56 on 21/04/17.
*/
public enum MessageType {
HEARTBEAT(1), RACESTATUS(12), DISPLAYTEXTMESSAGE(20),
XMLMESSAGE(26), RACESTARTSTATUS(27), YACHTEVENTCODE(29), YACHTACTIONCODE(31),
CHATTERTEXT(36), BOATLOCATION(37), MARKROUNDING(38), COURSEWIND(44), AVGWIND(47), NOTAMESSAGE(0);
private byte value;
private MessageType(int value) { this.value = (byte)value; }
public byte getValue() {
return value;
}
public static MessageType valueOf(byte bite){
switch(bite){
case 1:
return HEARTBEAT;
case 12:
return RACESTATUS;
case 20:
return DISPLAYTEXTMESSAGE;
case 26:
return XMLMESSAGE;
case 27:
return RACESTARTSTATUS;
case 29:
return YACHTEVENTCODE;
case 31:
return YACHTACTIONCODE;
case 36:
return CHATTERTEXT;
case 37:
return BOATLOCATION;
case 38:
return MARKROUNDING;
case 44:
return COURSEWIND;
case 47:
return AVGWIND;
default:
return NOTAMESSAGE;
}
}
}

@ -2,9 +2,12 @@ package seng302.Networking;
import org.junit.Assert;
import org.junit.Test;
import seng302.Networking.Exceptions.InvalidMessageException;
import seng302.Networking.MessageDecoders.XMLMessageDecoder;
import seng302.Networking.MessageEncoders.XMLMessageEncoder;
import seng302.Networking.Utils.MessageType;
import seng302.Networking.Messages.AC35Data;
import seng302.Networking.Messages.Enums.MessageType;
import seng302.Networking.Messages.XMLMessage;
import java.io.*;
@ -30,19 +33,32 @@ public class BinaryMessageDecoderTest {
BinaryMessageEncoder testMessage = new BinaryMessageEncoder(MessageType.XMLMESSAGE, time, 1, (short)encodedMessage.length, encodedMessage);
BinaryMessageDecoder testDecoder = new BinaryMessageDecoder(testMessage.getFullMessage());
testDecoder.decode();
BinaryMessageDecoder decoder = new BinaryMessageDecoder(testMessage.getFullMessage());
AC35Data message = null;
try {
message = decoder.decode();
}
catch (InvalidMessageException e) {
Assert.assertFalse(e.getMessage(), true);
}
if (!(message instanceof XMLMessage)){
Assert.assertFalse(true);
}
XMLMessage xmlMessage = (XMLMessage) message;
//message length
Assert.assertEquals((short) encodedMessage.length,testDecoder.getMessageLength());
Assert.assertEquals((short) encodedMessage.length, decoder.getMessageBodyLength());
//time stamp
Assert.assertEquals(time,testDecoder.getTimeStamp());
Assert.assertEquals(time, decoder.getHeaderTimeStamp());
//source ID
Assert.assertEquals((short) 1, testDecoder.getSourceID());
Assert.assertEquals((short) 1, decoder.getHeaderSourceID());
//message type
Assert.assertEquals(26, testDecoder.getMessageType());
Assert.assertEquals(26, decoder.getHeaderMessageType());
XMLMessageDecoder decoderXML = new XMLMessageDecoder(testDecoder.getMessage());
XMLMessageDecoder decoderXML = new XMLMessageDecoder(decoder.getMessageBody());
decoderXML.decode();
//tests from seng302.Networking.MessageDecoders.XMLMessageDecoderTest to make sure the file is still good
@ -62,7 +78,7 @@ public class BinaryMessageDecoderTest {
// Assert.assertEquals(xmlString.toString(), contents);
}catch (IOException e){
e.printStackTrace();
Assert.assertFalse(e.getMessage(), true);
}
}
}

@ -2,7 +2,7 @@ package seng302.Networking.MessageDecoders;
import org.junit.Assert;
import org.junit.Test;
import seng302.Networking.Utils.BoatLocationMessage;
import seng302.Networking.Messages.BoatLocation;
import seng302.Networking.MessageEncoders.RaceVisionByteEncoder;
@ -13,7 +13,7 @@ public class BoatLocationDecoderTest {
@Test
public void getByteArrayTest(){
long time = System.currentTimeMillis();
BoatLocationMessage testMessage = new BoatLocationMessage((byte) 1, time, 2,
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,
@ -22,7 +22,7 @@ public class BoatLocationDecoderTest {
byte [] testEncodedMessage = raceVisionByteEncoder.boatLocation(testMessage);
BoatLocationDecoder testDecoder = new BoatLocationDecoder(testEncodedMessage);
BoatLocationMessage decodedTest = testDecoder.getMessage();
BoatLocation decodedTest = testDecoder.getMessage();
Assert.assertEquals(testMessage.getMessageVersionNumber(), decodedTest.getMessageVersionNumber());
Assert.assertEquals(testMessage.getTime(), decodedTest.getTime());

@ -2,7 +2,7 @@ package seng302.Networking.MessageDecoders;
import org.junit.Assert;
import org.junit.Test;
import seng302.Networking.Utils.CourseWind;
import seng302.Networking.Messages.CourseWind;
import seng302.Networking.MessageEncoders.RaceVisionByteEncoder;
import java.util.ArrayList;

@ -3,8 +3,8 @@ package seng302.Networking.MessageDecoders;
import org.junit.Assert;
import org.junit.Test;
import seng302.Networking.MessageEncoders.RaceVisionByteEncoder;
import seng302.Networking.Utils.BoatStatusMessage;
import seng302.Networking.Utils.RaceStatus;
import seng302.Networking.Messages.BoatStatus;
import seng302.Networking.Messages.RaceStatus;
import java.util.ArrayList;
@ -32,8 +32,8 @@ public class RaceStatusDecoderTest {
long boat1TimeAtFinish = boat1TimeAtNextMark + (1000 * 15);
long boat2TimeAtFinish = boat2TimeAtNextMark + (1000 * 7);
BoatStatusMessage boatStatusMessage1 = new BoatStatusMessage(boat1SourceID, boat1Status, boat1LegNumber, boat1PenaltiesAwarded, boat1PenaltiesServed, boat1TimeAtNextMark, boat1TimeAtFinish);
BoatStatusMessage boatStatusMessage2 = new BoatStatusMessage(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;
int raceStatus = 3;
@ -41,11 +41,11 @@ public class RaceStatusDecoderTest {
int windDirection = 2341;
int windSpeed = 10201;
int raceType = 1;
ArrayList<BoatStatusMessage> boatStatusMessages = new ArrayList<>(2);
boatStatusMessages.add(boatStatusMessage1);
boatStatusMessages.add(boatStatusMessage2);
ArrayList<BoatStatus> boatStatuses = new ArrayList<>(2);
boatStatuses.add(boatStatus1);
boatStatuses.add(boatStatus2);
RaceStatus raceStatusObject = new RaceStatus(time, raceID, raceStatus, raceStartTime, windDirection, windSpeed, raceType, boatStatusMessages);
RaceStatus raceStatusObject = new RaceStatus(time, raceID, raceStatus, raceStartTime, windDirection, windSpeed, raceType, boatStatuses);
byte[] encodedRaceStatus = RaceVisionByteEncoder.raceStatus(raceStatusObject);
@ -61,7 +61,7 @@ public class RaceStatusDecoderTest {
Assert.assertEquals(windSpeed, decoderTest.getRaceWindSpeed());
BoatStatusMessage boat1 = decoderTest.getBoats().get(0);
BoatStatus boat1 = decoderTest.getBoats().get(0);
Assert.assertEquals(boat1SourceID, boat1.getSourceID());
Assert.assertEquals(boat1Status, boat1.getBoatStatus());

@ -163,7 +163,7 @@ public class StartController extends Controller implements Observer {
Platform.runLater(() -> {
if (!this.hasRaceStarted) {
if(visualiserInput.getRaceStatus() == null) {
return;//TEMP
return;//TEMP
}
else {
this.hasRaceStarted = true;

@ -28,7 +28,7 @@ public class StreamedCourse extends Observable implements RaceDataSource {
public void setBoatXMLReader(BoatXMLReader boatXMLReader) {
this.boatXMLReader = boatXMLReader;
if (streamedCourseXMLReader != null && boatXMLReader != null) {
boatXMLReader.setParticipants(streamedCourseXMLReader.getParticipants());
this.boatXMLReader.setParticipants(streamedCourseXMLReader.getParticipants());
boatXMLReader.read();
}

@ -6,9 +6,9 @@ import seng302.Model.Boat;
import seng302.Model.Leg;
import seng302.Model.Marker;
import seng302.Model.Race;
import seng302.Networking.Utils.BoatStatusMessage;
import seng302.Networking.Utils.Enums.BoatStatus;
import seng302.Networking.Utils.BoatLocationMessage;
import seng302.Networking.Messages.BoatStatus;
import seng302.Networking.Messages.BoatLocation;
import seng302.Networking.Messages.Enums.BoatStatusEnum;
import seng302.VisualiserInput;
/**
@ -55,9 +55,9 @@ public class StreamedRace extends Race {
*/
protected void checkPosition(Boat boat, long timeElapsed) {
StreamedCourse raceData = visualiserInput.getCourse();
BoatStatusMessage boatStatusMessage = visualiserInput.getBoatStatus().get(boat.getSourceID());
BoatStatus boatStatusMessage = visualiserInput.getBoatStatusMap().get(boat.getSourceID());
if (boatStatusMessage != null) {
BoatStatus boatStatus = BoatStatus.valueOf(boatStatusMessage.getBoatStatus());
BoatStatusEnum boatStatusEnum = BoatStatusEnum.fromByte(boatStatusMessage.getBoatStatus());
int legNumber = boatStatusMessage.getLegNumber();
@ -65,11 +65,11 @@ public class StreamedRace extends Race {
boat.setCurrentLeg(legs.get(legNumber));
}
if (boatStatus == BoatStatus.RACING) {
if (boatStatusEnum == BoatStatusEnum.RACING) {
boat.addTrackPoint(boat.getCurrentPosition());
} else if (boatStatus == BoatStatus.DNF) {
} else if (boatStatusEnum == BoatStatusEnum.DNF) {
boat.setDnf(true);
} else if (boatStatus == BoatStatus.FINISHED || legNumber == raceData.getLegs().size()) {
} else if (boatStatusEnum == BoatStatusEnum.FINISHED || legNumber == raceData.getLegs().size()) {
boatsFinished++;
boat.setTimeFinished(timeElapsed);
boat.setFinished(true);
@ -88,13 +88,13 @@ public class StreamedRace extends Race {
*/
protected void updatePosition(Boat boat, int millisecondsElapsed) {
int sourceID = boat.getSourceID();
BoatLocationMessage boatLocationMessage = visualiserInput.getBoatLocationMessage(sourceID);
if(boatLocationMessage != null) {
double lat = boatLocationMessage.getLatitudeDouble();
double lon = boatLocationMessage.getLongitudeDouble();
BoatLocation boatLocation = visualiserInput.getBoatLocationMessage(sourceID);
if(boatLocation != null) {
double lat = boatLocation.getLatitudeDouble();
double lon = boatLocation.getLongitudeDouble();
boat.setCurrentPosition(new GPSCoordinate(lat, lon));
boat.setHeading(boatLocationMessage.getHeadingDegrees());
boat.setVelocity(boatLocationMessage.getBoatSOG() * MMPS_TO_KN);
boat.setHeading(boatLocation.getHeadingDegrees());
boat.setVelocity(boatLocation.getBoatSOG() * MMPS_TO_KN);
}
}

@ -1,77 +1,89 @@
package seng302;
import org.opengis.style.Mark;
import org.xml.sax.SAXException;
import seng302.Mock.*;
import seng302.Networking.BinaryMessageDecoder;
import seng302.Networking.MessageDecoders.*;
import seng302.Networking.Utils.*;
import seng302.Networking.Exceptions.InvalidMessageException;
import seng302.Networking.Messages.*;
import javax.xml.parsers.ParserConfigurationException;
import java.io.*;
import java.net.*;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.text.ParseException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.ArrayBlockingQueue;
import static seng302.Networking.Utils.ByteConverter.bytesToInt;
import static seng302.Networking.Utils.ByteConverter.bytesToShort;
import static seng302.Networking.Utils.MessageType.*;
/**
* TCP server to act as the mock AC35 streaming interface
* TCP client which receives packets/messages from a race data source (e.g., mock source, official source), and exposes them to any observers.
*/
public class VisualiserInput implements Runnable
{
//Time since last heartbeat.
private long lastHeartbeatTime;
public class VisualiserInput implements Runnable {
//socket port 4945 as 4940 is ac35 live port and 4941 is ac35 test port
private Socket connectionSocket;
///A queue that contains messages that have been received, and need to be handled.
private ArrayBlockingQueue<AC35Data> messagesReceivedQueue = new ArrayBlockingQueue<>(1000000);//We have room for 1,000,000. Is this much capacity actually needed?
long heartbeatSeqNum;
///Timestamp of the last heartbeat.
private long lastHeartbeatTime = -1;
///Sequence number of the last heartbeat.
private long lastHeartbeatSequenceNum = -1;
///How long we should wait for a heartbeat before assuming the connection is dead. Measured in milliseconds.
private long heartBeatPeriod = 10 * 1000;
private StreamedCourse course = null;
///The socket that we have connected to.
private Socket connectionSocket;
private Map<Integer, BoatLocationMessage> boatLocation;
///Object to store parsed course data. //TODO comment?
private StreamedCourse course;
///The last RaceStatus message received.
private RaceStatus raceStatus;
private Map<Integer, BoatStatusMessage> boatStatus;
///A map of the last BoatStatus message received, for each boat.
private Map<Integer, BoatStatus> boatStatusMap = new HashMap<>();;
///A map of the last BoatLocation message received, for each boat.
private Map<Integer, BoatLocation> boatLocationMap = new HashMap<>();
///The last AverageWind message received.
private AverageWind averageWind;
///The last CourseWinds message received.
private CourseWinds courseWinds;
private Map<Integer, MarkRounding> markRounding;
///A map of the last MarkRounding message received, for each boat.
private Map<Integer, MarkRounding> markRoundingMap = new HashMap<>();
///The last RaceStartStatus message received.
private RaceStartStatus raceStartStatus;
private BufferedInputStream inStream;
///InputStream (from the socket).
private DataInputStream inStream;
///TODO comment?
private boolean receiverLoop = true;
public VisualiserInput(Socket socket, StreamedCourse course) throws IOException{
/**
* Ctor.
* @param socket Socket from which we will receive race data.
* @param course TODO comment?
* @throws IOException If there is something wrong with the socket's input stream.
*/
public VisualiserInput(Socket socket, StreamedCourse course) throws IOException {
this.connectionSocket = socket;
// this.connectionSocket = new Socket("livedata.americascup.com",4941);
this.inStream = new BufferedInputStream(connectionSocket.getInputStream());
//We wrap a DataInputStream around the socket's InputStream because it has the stream.readFully(buffer) function, which is a blocking read until the buffer has been filled.
this.inStream = new DataInputStream(connectionSocket.getInputStream());
this.course = course;
this.boatLocation = new HashMap<>();
this.boatStatus = new HashMap<>();
this.markRounding = new HashMap<>();
//start Time
this.lastHeartbeatTime = System.currentTimeMillis();
}
/**
* Provides StreamedCourse container for fixed course data
* @return course for current VisualiserInput instance
* Provides StreamedCourse container for fixed course data.
* @return Course for current VisualiserInput instance.
* @see seng302.Mock.StreamedCourse
*/
public StreamedCourse getCourse() {
@ -80,228 +92,342 @@ public class VisualiserInput implements Runnable
/**
* Returns the last boat location message associated with the given boat source ID.
* @param sourceID unique global identifier for boat
* @return most recent location message
* @param sourceID Unique global identifier for the boat.
* @return The most recent location message.
*/
public BoatLocationMessage getBoatLocationMessage(int sourceID) {
return boatLocation.get(sourceID);
public BoatLocation getBoatLocationMessage(int sourceID) {
return boatLocationMap.get(sourceID);
}
/**
* calculates the time since last heartbeat
* @return time since last heartbeat
* Calculates the time since last heartbeat, in milliseconds.
* @return Time since last heartbeat, in milliseconds..
*/
private double timeSinceHeartbeat() {
long now = System.currentTimeMillis();
return (now - lastHeartbeatTime) / 1000.0;
return (now - lastHeartbeatTime);
}
/**
* Returns the boat locations
* @return locations of where the boats are
* Returns the boat locations map. Maps between Integer (Boat ID) -> BoatLocation.
* @return Map of boat locations.
*/
public Map<Integer, BoatLocationMessage> getBoatLocation() {
return boatLocation;
public Map<Integer, BoatLocation> getBoatLocationMap() {
return boatLocationMap;
}
/**
* Gets the status of the race
* @return the status of the race
* Gets the status of the race.
* @return The status of the race.
*/
public RaceStatus getRaceStatus() {
return raceStatus;
}
/**
* Hashmap of the boat status'
* @return Hash map of boat status
* Returns the boat statuses map. Maps between Integer (Boat ID) -> BoatStatus.
* @return Map of boat statuses.
*/
public Map<Integer, BoatStatusMessage> getBoatStatus() {
return boatStatus;
public Map<Integer, BoatStatus> getBoatStatusMap() {
return boatStatusMap;
}
/**
* Returns the average wind of the race
* @return average wind in the race
* Returns the average wind of the race.
* @return Average wind in the race.
*/
public AverageWind getAverageWind() {
return averageWind;
}
/**
* Returns winds in the course
* @return winds that are in the course
* Returns winds in the course.
* @return Winds that are in the course.
*/
public CourseWinds getCourseWinds() {
return courseWinds;
}
/**
* Returns get Mark Rounding Boat Source ID, MarkRound
* @return the mark rounding
* Returns the mark roundings map. Maps between Integer (Boat ID) -> MarkRounding.
* @return Map of mark roundings.
*/
public Map<Integer, MarkRounding> getMarkRounding() {
return markRounding;
public Map<Integer, MarkRounding> getMarkRoundingMap() {
return markRoundingMap;
}
/**
* Takes an inputStream and reads the first 15 bytes (the header) and gets the message length
* for the whole message then reads that and returns the byte array
* @return encoded binary messsage bytes
* @throws IOException made by the inputstream reading
* Reads and returns the next message as an array of bytes from the socket. Use getNextMessage() to get the actual message object instead.
* @return Encoded binary message bytes.
* @throws IOException Thrown when an error occurs while reading from the socket.
*/
private byte[] getBytes() throws IOException {
inStream.mark(0);
if (inStream.available() < 15) return null;//if there is not enough bytes foer the headerr
byte[] headerBytes = new byte[15];
inStream.read(headerBytes);
byte[] messageLenBytes = Arrays.copyOfRange(headerBytes, 13, 15);
short messageLen = bytesToShort(messageLenBytes);
byte[] messageBytesWithCRC = new byte[messageLen+4];
if (inStream.available() < messageLen + 4){//if the message is not long enough
inStream.reset();//reset pointer.
return null;
}
inStream.read(messageBytesWithCRC, 0, messageLen + 4);
ByteBuffer binaryMessageBytes = ByteBuffer.allocate(headerBytes.length+messageBytesWithCRC.length);
binaryMessageBytes.put(headerBytes);
binaryMessageBytes.put(messageBytesWithCRC);
return binaryMessageBytes.array();
private byte[] getNextMessageBytes() throws IOException {
short CRCLength = 4;
short headerLength = 15;
//Read the header of the next message.
byte[] headerBytes = new byte[headerLength];
inStream.readFully(headerBytes);
//Read the message body length.
byte[] messageBodyLengthBytes = Arrays.copyOfRange(headerBytes, headerLength - 2, headerLength);
short messageBodyLength = bytesToShort(messageBodyLengthBytes);
//Read the message body.
byte[] messageBodyBytes = new byte[messageBodyLength];
inStream.readFully(messageBodyBytes);
//Read the message CRC.
byte[] messageCRCBytes = new byte[CRCLength];
inStream.readFully(messageCRCBytes);
//Put the head + body + crc into one large array.
ByteBuffer messageBytes = ByteBuffer.allocate(headerBytes.length + messageBodyBytes.length + messageCRCBytes.length);
messageBytes.put(headerBytes);
messageBytes.put(messageBodyBytes);
messageBytes.put(messageCRCBytes);
return messageBytes.array();
}
/**
* Reads and returns the next message object from the socket.
* @return The message object. Use instanceof for concrete type.
* @throws IOException Thrown when an error occurs while reading from the socket.
* @throws InvalidMessageException Thrown when the message is invalid in some way.
*/
private AC35Data getNextMessage() throws IOException, InvalidMessageException
{
//Get the next message from the socket as a block of bytes.
byte[] messageBytes = this.getNextMessageBytes();
//Decode the binary message into an appropriate message object.
BinaryMessageDecoder decoder = new BinaryMessageDecoder(messageBytes);
AC35Data message = decoder.decode();
return message;
}
/**
* Main loop which reads messages from the socket, and exposes them.
*/
public void run(){
this.receiverLoop = true;
try{
//receiver loop that gets the input
while(receiverLoop) {
//if no heartbeat has been received in more than 6 seconds
//the connection will need to be restarted
if (timeSinceHeartbeat() > 6){
System.out.println("Connection has stopped, trying to reconnect");
receiverLoop = false;
//receiver loop that gets the input
while (receiverLoop) {
//If no heartbeat has been received in more the heartbeat period
//then the connection will need to be restarted.
System.out.println("time since last heartbeat: " + timeSinceHeartbeat());//TEMP REMOVE
if (timeSinceHeartbeat() > this.heartBeatPeriod) {
System.out.println("Connection has stopped, trying to reconnect.");
//Attempt to reconnect the socket.
try {//This attempt doesn't really work. Under what circumstances would
this.connectionSocket = new Socket(this.connectionSocket.getInetAddress(), this.connectionSocket.getPort());
//this.connectionSocket.connect(this.connectionSocket.getRemoteSocketAddress());
//Reset the heartbeat timer.
this.lastHeartbeatTime = System.currentTimeMillis();
}
catch (IOException e) {
System.err.println("Unable to reconnect.");
//Wait 500ms. Ugly hack, should refactor.
long waitPeriod = 500;
long waitTimeStart = System.currentTimeMillis() + waitPeriod;
while (System.currentTimeMillis() < waitTimeStart){
//Nothing. Busyloop.
}
//Swallow the exception.
continue;
}
}
//Reads the next message.
AC35Data message = null;
try {
message = this.getNextMessage();
}
catch (InvalidMessageException | IOException e) {
//Prints exception to stderr, and iterate loop (that is, read the next message).
System.err.println("Unable to read message: " + e.getMessage());
//Continue to the next loop iteration/message.
continue;
}
//Add it to message queue.
this.messagesReceivedQueue.add(message);
//Checks which message is being received and does what is needed for that message.
//Heartbeat.
if (message instanceof Heartbeat) {
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) {
lastHeartbeatTime = System.currentTimeMillis();
lastHeartbeatSequenceNum = heartbeat.getSequenceNumber();
//System.out.println("HeartBeat Message! " + lastHeartbeatSequenceNum);
}
//converts the input into a byte array that can be read by the decoder
byte[] binaryMessage = getBytes();
//if there is no bytes read.
if (binaryMessage == null){
continue;
}
//RaceStatus.
else if (message instanceof RaceStatus) {
RaceStatus raceStatus = (RaceStatus) message;
//System.out.println("Race Status Message");
this.raceStatus = raceStatus;
for (BoatStatus boatStatus: this.raceStatus.getBoatStatuses()) {
this.boatStatusMap.put(boatStatus.getSourceID(), boatStatus);
}
}
//DisplayTextMessage.
/*else if (message instanceof DisplayTextMessage) {
//System.out.println("Display Text Message");
//No decoder for this.
}*/
//XMLMessage.
else if (message instanceof XMLMessage) {
XMLMessage xmlMessage = (XMLMessage) message;
//System.out.println("XML Message!");
if (xmlMessage.getXmlMsgSubType() == xmlMessage.XMLTypeRegatta){
//System.out.println("Setting Regatta");
try {
course.setRegattaXMLReader(new RegattaXMLReader(xmlMessage.getXmlMessage()));
}
//TODO REFACTOR should put all of these exceptions behind a RegattaXMLReaderException.
catch (IOException | SAXException | ParserConfigurationException e) {
System.err.println("Error creating RegattaXMLReader: " + e.getMessage());
//Continue to the next loop iteration/message.
continue;
}
} else if (xmlMessage.getXmlMsgSubType() == xmlMessage.XMLTypeRace){
//System.out.println("Setting Course");
try {
course.setStreamedCourseXMLReader(new StreamedCourseXMLReader(xmlMessage.getXmlMessage()));
}
//TODO REFACTOR should put all of these exceptions behind a StreamedCourseXMLReaderException.
catch (IOException | SAXException | ParserConfigurationException | ParseException | StreamedCourseXMLException e) {
System.err.println("Error creating StreamedCourseXMLReader: " + e.getMessage());
//Continue to the next loop iteration/message.
continue;
}
} else if (xmlMessage.getXmlMsgSubType() == xmlMessage.XMLTypeBoat){
//System.out.println("Setting Boats");
try
{
course.setBoatXMLReader(new BoatXMLReader(xmlMessage.getXmlMessage()));
}
//TODO REFACTOR should put all of these exceptions behind a BoatXMLReaderException.
catch (IOException | SAXException | ParserConfigurationException e) {
System.err.println("Error creating BoatXMLReader: " + e.getMessage());
//Continue to the next loop iteration/message.
continue;
}
//decode the binary message into readable date
BinaryMessageDecoder testDecoder = new BinaryMessageDecoder(binaryMessage);
AC35Data data = testDecoder.decode();
if (data == null){
continue;
}
//checks which message is being received and does what is needed for that message
MessageType mType = data.getType();
switch (mType) {
case HEARTBEAT:
lastHeartbeatTime = System.currentTimeMillis();
//note: if the program runs for over 340 years, this will crash.
heartbeatSeqNum = ByteConverter.bytesToLong(testDecoder.getMessage());
// System.out.println("HeartBeat Message! " + heartbeatSeqNum);
break;
case RACESTATUS:
// System.out.println("Race Status Message");
raceStatus = ((RaceStatus) data);
for (BoatStatusMessage msg: raceStatus.getBoatStatusMessages()){
boatStatus.put(msg.getSourceID(), msg);
}
break;
case DISPLAYTEXTMESSAGE:
// System.out.println("Display Text Message");
//no decoder for this.
break;
case XMLMESSAGE:
System.out.println("XML Message!");
XMLMessage xml = (XMLMessage) data;
try {
if (xml.getXmlMsgSubType() == xml.XMLTypeRegatta){
System.out.println("Setting Regatta");
course.setRegattaXMLReader(new RegattaXMLReader(xml.getXmlMessage()));
} else if (xml.getXmlMsgSubType() == xml.XMLTypeRace){
System.out.println("Setting Course");
course.setStreamedCourseXMLReader(new StreamedCourseXMLReader(xml.getXmlMessage()));
} else if (xml.getXmlMsgSubType() == xml.XMLTypeBoat){
System.out.println("Setting Boats");
course.setBoatXMLReader(new BoatXMLReader(xml.getXmlMessage()));
}
} catch (SAXException e) {
e.printStackTrace();
} catch (ParserConfigurationException e) {
e.printStackTrace();
} catch (ParseException e) {
e.printStackTrace();
} catch (StreamedCourseXMLException e) {
e.printStackTrace();
}
break;
case RACESTARTSTATUS:
//System.out.println("Race Start Status Message");
RaceStartStatus rSS = (RaceStartStatus) data;
raceStartStatus = rSS;
break;
case YACHTEVENTCODE:
// System.out.println("Yacht Action Code!");
//no decoder
break;
case YACHTACTIONCODE:
// System.out.println("Yacht Action Code!");
//no decoder
break;
case CHATTERTEXT:
// System.out.println("Chatter Text Message!");
//no decoder
break;
case BOATLOCATION:
// System.out.println("Boat Location!");
BoatLocationMessage msg = (BoatLocationMessage) data;
if (boatLocation.containsKey(msg.getSourceID())){
if (msg.getTime() > boatLocation.get(msg.getSourceID()).getTime()){
boatLocation.put(msg.getSourceID(), msg);
}
}else{
boatLocation.put(msg.getSourceID(), msg);
}
// System.out.println("Boat Location Message!");
break;
case MARKROUNDING:
// System.out.println("Mark Rounding Message!");
MarkRounding mkrounding = (MarkRounding) data;
markRounding.put(mkrounding.getSourceID(), mkrounding);
break;
case COURSEWIND:
// System.out.println("Course Wind Message!");
courseWinds = (CourseWinds) data;
break;
case AVGWIND:
// System.out.println("Average Wind Message!");
AverageWind avgWind = (AverageWind) data;
averageWind = avgWind;
break;
default:
System.out.println("Broken Message!");
break;
}
//RaceStartStatus.
else if (message instanceof RaceStartStatus) {
RaceStartStatus raceStartStatus = (RaceStartStatus) message;
//System.out.println("Race Start Status Message");
this.raceStartStatus = raceStartStatus;
}
//YachtEventCode.
/*else if (message instanceof YachtEventCode) {
YachtEventCode yachtEventCode = (YachtEventCode) message;
//System.out.println("Yacht Event Code!");
//No decoder for this.
}*/
//YachtActionCode.
/*else if (message instanceof YachtActionCode) {
YachtActionCode yachtActionCode = (YachtActionCode) message;
//System.out.println("Yacht Action Code!");
//No decoder for this.
}*/
//ChatterText.
/*else if (message instanceof ChatterText) {
ChatterText chatterText = (ChatterText) message;
//System.out.println("Chatter Text Message!");
//No decoder for this.
}*/
//BoatLocation.
else if (message instanceof BoatLocation) {
BoatLocation boatLocation = (BoatLocation) message;
//System.out.println("Boat Location!");
if (this.boatLocationMap.containsKey(boatLocation.getSourceID())) {
//If our boatlocation map already contains a boat location message for this boat, check that the new message is actually for a later timestamp (i.e., newer).
if (boatLocation.getTime() > this.boatLocationMap.get(boatLocation.getSourceID()).getTime()){
//If it is, replace the old message.
this.boatLocationMap.put(boatLocation.getSourceID(), boatLocation);
}
}else{
//If the map _doesn't_ already contain a message for this boat, insert the message.
this.boatLocationMap.put(boatLocation.getSourceID(), boatLocation);
}
}
}catch(IOException e){
e.printStackTrace();
}
}
//MarkRounding.
else if (message instanceof MarkRounding) {
MarkRounding markRounding = (MarkRounding) message;
//System.out.println("Mark Rounding Message!");
if (this.markRoundingMap.containsKey(markRounding.getSourceID())) {
//If our markRoundingMap already contains a mark rounding message for this boat, check that the new message is actually for a later timestamp (i.e., newer).
if (markRounding.getTime() > this.markRoundingMap.get(markRounding.getSourceID()).getTime()){
//If it is, replace the old message.
this.markRoundingMap.put(markRounding.getSourceID(), markRounding);
}
}else{
//If the map _doesn't_ already contain a message for this boat, insert the message.
this.markRoundingMap.put(markRounding.getSourceID(), markRounding);
}
public boolean isReceiverLoop() {
return receiverLoop;
}
}
//CourseWinds.
else if (message instanceof CourseWinds) {
CourseWinds courseWinds = (CourseWinds) message;
public static void main(String argv[]) throws Exception
{
Socket socket = new Socket("livedata.americascup.com", 4941);
// Socket socket = new Socket(InetAddress.getLocalHost(), 4942);
VisualiserInput receiver = new VisualiserInput(socket, new StreamedCourse());
receiver.run();
//System.out.println("Course Wind Message!");
this.courseWinds = courseWinds;
}
//AverageWind.
else if (message instanceof AverageWind) {
AverageWind averageWind = (AverageWind) message;
//System.out.println("Average Wind Message!");
this.averageWind = averageWind;
}
//Unrecognised message.
else {
System.out.println("Broken Message!");
}
}
}
}

Loading…
Cancel
Save