commit
5bc51343f5
@ -0,0 +1,196 @@
|
||||
package mock.model;
|
||||
|
||||
import javafx.animation.AnimationTimer;
|
||||
import mock.model.commandFactory.Command;
|
||||
import mock.model.commandFactory.CommandFactory;
|
||||
import mock.model.commandFactory.CompositeCommand;
|
||||
import network.Messages.Enums.BoatActionEnum;
|
||||
import network.Messages.Enums.BoatStatusEnum;
|
||||
import network.Messages.Enums.RaceStatusEnum;
|
||||
import network.Messages.LatestMessages;
|
||||
import visualiser.gameController.ControllerServer;
|
||||
|
||||
import java.util.Observable;
|
||||
import java.util.Observer;
|
||||
import java.util.Stack;
|
||||
|
||||
public class RaceLogic implements Observer, Runnable {
|
||||
/**
|
||||
* State of current race modified by this object
|
||||
*/
|
||||
private MockRace race;
|
||||
/**
|
||||
* High-level interface to AC35 protocol server
|
||||
*/
|
||||
private RaceServer server;
|
||||
|
||||
private CompositeCommand commands;
|
||||
|
||||
/**
|
||||
* Initialises race loop with state and server message queue
|
||||
* @param race state of race to modify
|
||||
* @param messages to send to server
|
||||
*/
|
||||
public RaceLogic(MockRace race, LatestMessages messages) {
|
||||
this.race = race;
|
||||
this.server = new RaceServer(race, messages);
|
||||
this.commands = new CompositeCommand();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialise boats and start countdown timer
|
||||
*/
|
||||
@Override
|
||||
public void run() {
|
||||
race.initialiseBoats();
|
||||
this.countdownTimer.start();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Countdown timer until race starts.
|
||||
*/
|
||||
protected AnimationTimer countdownTimer = new AnimationTimer() {
|
||||
|
||||
|
||||
long currentTime = System.currentTimeMillis();
|
||||
|
||||
@Override
|
||||
public void handle(long arg0) {
|
||||
|
||||
//Update race time.
|
||||
race.updateRaceTime(currentTime);
|
||||
|
||||
//Update the race status based on the current time.
|
||||
race.updateRaceStatusEnum();
|
||||
|
||||
//Provide boat's with an estimated time at next mark until the race starts.
|
||||
race.setBoatsTimeNextMark(race.getRaceClock().getCurrentTime());
|
||||
|
||||
//Parse the boat locations.
|
||||
server.parseBoatLocations();
|
||||
|
||||
//Parse the marks.
|
||||
server.parseMarks();
|
||||
|
||||
// Change wind direction
|
||||
race.changeWindDirection();
|
||||
|
||||
//Parse the race status.
|
||||
server.parseRaceStatus();
|
||||
|
||||
|
||||
if (race.getRaceStatusEnum() == RaceStatusEnum.STARTED) {
|
||||
race.setBoatsStatusToRacing();
|
||||
raceTimer.start();
|
||||
this.stop();
|
||||
}
|
||||
|
||||
//Update the animations timer's time.
|
||||
currentTime = System.currentTimeMillis();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Timer that runs for the duration of the race, until all boats finish.
|
||||
*/
|
||||
private AnimationTimer raceTimer = new AnimationTimer() {
|
||||
|
||||
/**
|
||||
* Start time of loop, in milliseconds.
|
||||
*/
|
||||
long timeRaceStarted = System.currentTimeMillis();
|
||||
|
||||
/**
|
||||
* Current time during a loop iteration.
|
||||
*/
|
||||
long currentTime = System.currentTimeMillis();
|
||||
|
||||
/**
|
||||
* The time of the previous frame, in milliseconds.
|
||||
*/
|
||||
long lastFrameTime = timeRaceStarted;
|
||||
|
||||
long framePeriod = currentTime - lastFrameTime;
|
||||
|
||||
@Override
|
||||
public void handle(long arg0) {
|
||||
|
||||
//Get the current time.
|
||||
currentTime = System.currentTimeMillis();
|
||||
|
||||
//Update race time.
|
||||
race.updateRaceTime(currentTime);
|
||||
|
||||
//As long as there is at least one boat racing, we still simulate the race.
|
||||
if (race.getNumberOfActiveBoats() != 0) {
|
||||
|
||||
//Get the time period of this frame.
|
||||
framePeriod = currentTime - lastFrameTime;
|
||||
|
||||
//For each boat, we update its position, and generate a BoatLocationMessage.
|
||||
for (MockBoat boat : race.getBoats()) {
|
||||
|
||||
//If it is still racing, update its position.
|
||||
if (boat.getStatus() == BoatStatusEnum.RACING) {
|
||||
commands.execute();
|
||||
race.updatePosition(boat, framePeriod, race.getRaceClock().getDurationMilli());
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
//Otherwise, the race is over!
|
||||
raceFinished.start();
|
||||
race.setRaceStatusEnum(RaceStatusEnum.FINISHED);
|
||||
this.stop();
|
||||
}
|
||||
|
||||
if (race.getNumberOfActiveBoats() != 0) {
|
||||
// Change wind direction
|
||||
race.changeWindDirection();
|
||||
|
||||
//Parse the boat locations.
|
||||
server.parseBoatLocations();
|
||||
|
||||
//Parse the marks.
|
||||
server.parseMarks();
|
||||
|
||||
//Parse the race status.
|
||||
server.parseRaceStatus();
|
||||
|
||||
|
||||
//Update the last frame time.
|
||||
this.lastFrameTime = currentTime;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Broadcast that the race has finished.
|
||||
*/
|
||||
protected AnimationTimer raceFinished = new AnimationTimer(){
|
||||
int iters = 0;
|
||||
@Override
|
||||
public void handle(long now) {
|
||||
|
||||
server.parseRaceStatus();
|
||||
|
||||
if (iters > 500) {
|
||||
stop();
|
||||
}
|
||||
iters++;
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public void update(Observable o, Object arg) {
|
||||
ControllerServer server = (ControllerServer)o;
|
||||
BoatActionEnum action = server.getAction();
|
||||
MockBoat boat = race.getBoats().get(0);
|
||||
|
||||
commands.addCommand(CommandFactory.createCommand(race, boat, action));
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,156 @@
|
||||
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;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Created by connortaylorbrown on 2/08/17.
|
||||
*/
|
||||
public class RaceServer {
|
||||
private MockRace race;
|
||||
private LatestMessages latestMessages;
|
||||
|
||||
/**
|
||||
* The sequence number of the latest RaceStatus message sent or received.
|
||||
*/
|
||||
private int raceStatusSequenceNumber = 1;
|
||||
|
||||
/**
|
||||
* The sequence number of the latest BoatLocation message sent or received.
|
||||
*/
|
||||
private int boatLocationSequenceNumber = 1;
|
||||
|
||||
|
||||
public RaceServer(MockRace race, LatestMessages latestMessages) {
|
||||
this.race = race;
|
||||
this.latestMessages = latestMessages;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Parses an individual marker boat, and sends it to mockOutput.
|
||||
* @param mark The marker boat to parse.
|
||||
*/
|
||||
private void parseIndividualMark(Mark mark) {
|
||||
//Create message.
|
||||
BoatLocation boatLocation = new BoatLocation(
|
||||
mark.getSourceID(),
|
||||
mark.getPosition().getLatitude(),
|
||||
mark.getPosition().getLongitude(),
|
||||
this.boatLocationSequenceNumber,
|
||||
BoatLocationDeviceEnum.Mark,
|
||||
Bearing.fromDegrees(0),
|
||||
0,
|
||||
race.getRaceClock().getCurrentTimeMilli());
|
||||
|
||||
//Iterates the sequence number.
|
||||
this.boatLocationSequenceNumber++;
|
||||
|
||||
this.latestMessages.setBoatLocation(boatLocation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the compound marker boats through mock output.
|
||||
*/
|
||||
public void parseMarks() {
|
||||
for (CompoundMark compoundMark : race.getCompoundMarks()) {
|
||||
|
||||
//Get the individual marks from the compound mark.
|
||||
Mark mark1 = compoundMark.getMark1();
|
||||
Mark mark2 = compoundMark.getMark2();
|
||||
|
||||
//If they aren't null, parse them (some compound marks only have one mark).
|
||||
if (mark1 != null) {
|
||||
this.parseIndividualMark(mark1);
|
||||
}
|
||||
|
||||
if (mark2 != null) {
|
||||
this.parseIndividualMark(mark2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Parse the boats in the race, and send it to mockOutput.
|
||||
*/
|
||||
public void parseBoatLocations() {
|
||||
//Parse each boat.
|
||||
for (MockBoat boat : race.getBoats()) {
|
||||
this.parseIndividualBoatLocation(boat);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses an individual boat, and sends it to mockOutput.
|
||||
* @param boat The boat to parse.
|
||||
*/
|
||||
private void parseIndividualBoatLocation(MockBoat boat) {
|
||||
|
||||
BoatLocation boatLocation = new BoatLocation(
|
||||
boat.getSourceID(),
|
||||
boat.getCurrentPosition().getLatitude(),
|
||||
boat.getCurrentPosition().getLongitude(),
|
||||
this.boatLocationSequenceNumber,
|
||||
BoatLocationDeviceEnum.RacingYacht,
|
||||
boat.getBearing(),
|
||||
boat.getCurrentSpeed(),
|
||||
race.getRaceClock().getCurrentTimeMilli());
|
||||
|
||||
//Iterates the sequence number.
|
||||
this.boatLocationSequenceNumber++;
|
||||
|
||||
this.latestMessages.setBoatLocation(boatLocation);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Parses the race status, and sends it to mockOutput.
|
||||
*/
|
||||
public void parseRaceStatus() {
|
||||
|
||||
//A race status message contains a list of boat statuses.
|
||||
List<BoatStatus> boatStatuses = new ArrayList<>();
|
||||
|
||||
//Add each boat status to the status list.
|
||||
for (MockBoat boat : race.getBoats()) {
|
||||
|
||||
BoatStatus boatStatus = new BoatStatus(
|
||||
boat.getSourceID(),
|
||||
boat.getStatus(),
|
||||
boat.getCurrentLeg().getLegNumber(),
|
||||
boat.getEstimatedTimeAtNextMark().toInstant().toEpochMilli() );
|
||||
|
||||
boatStatuses.add(boatStatus);
|
||||
}
|
||||
|
||||
|
||||
|
||||
//Create race status object, and send it.
|
||||
RaceStatus raceStatus = new RaceStatus(
|
||||
RaceStatus.currentMessageVersionNumber,
|
||||
System.currentTimeMillis(),
|
||||
race.getRaceId(),
|
||||
race.getRaceStatusEnum(),
|
||||
race.getRaceClock().getStartingTimeMilli(),
|
||||
race.getWindDirection(),
|
||||
race.getWindSpeed(),
|
||||
race.getRaceType(),
|
||||
boatStatuses);
|
||||
|
||||
this.latestMessages.setRaceStatus(raceStatus);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,249 @@
|
||||
package mock.model;
|
||||
|
||||
|
||||
import shared.model.Bearing;
|
||||
import shared.model.Wind;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
/**
|
||||
* This class generates Wind objects for use in a MockRace.
|
||||
* Bounds on bearing and speed can be specified.
|
||||
* Wind can be completely random, or random incremental change.
|
||||
*/
|
||||
public class WindGenerator {
|
||||
|
||||
/**
|
||||
* The bearing the wind direction starts at.
|
||||
*/
|
||||
private Bearing windBaselineBearing;
|
||||
|
||||
/**
|
||||
* The lower bearing angle that the wind may have.
|
||||
*/
|
||||
private Bearing windBearingLowerBound;
|
||||
|
||||
/**
|
||||
* The upper bearing angle that the wind may have.
|
||||
*/
|
||||
private Bearing windBearingUpperBound;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* The speed the wind starts at, in knots.
|
||||
*/
|
||||
private double windBaselineSpeed;
|
||||
|
||||
/**
|
||||
* The lower speed that the wind may have, in knots.
|
||||
*/
|
||||
private double windSpeedLowerBound;
|
||||
|
||||
/**
|
||||
* The upper speed that the wind may have, in knots.
|
||||
*/
|
||||
private double windSpeedUpperBound;
|
||||
|
||||
|
||||
/**
|
||||
* Creates a wind generator, with a baseline, lower bound, and upper bound, for the wind speed and direction.
|
||||
* @param windBaselineBearing Baseline wind direction.
|
||||
* @param windBearingLowerBound Lower bound for wind direction.
|
||||
* @param windBearingUpperBound Upper bound for wind direction.
|
||||
* @param windBaselineSpeed Baseline wind speed, in knots.
|
||||
* @param windSpeedLowerBound Lower bound for wind speed, in knots.
|
||||
* @param windSpeedUpperBound Upper bound for wind speed, in knots.
|
||||
*/
|
||||
public WindGenerator(Bearing windBaselineBearing, Bearing windBearingLowerBound, Bearing windBearingUpperBound, double windBaselineSpeed, double windSpeedLowerBound, double windSpeedUpperBound) {
|
||||
|
||||
this.windBaselineBearing = windBaselineBearing;
|
||||
this.windBearingLowerBound = windBearingLowerBound;
|
||||
this.windBearingUpperBound = windBearingUpperBound;
|
||||
this.windBaselineSpeed = windBaselineSpeed;
|
||||
this.windSpeedLowerBound = windSpeedLowerBound;
|
||||
this.windSpeedUpperBound = windSpeedUpperBound;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Generates a wind object using the baseline wind speed and bearing.
|
||||
* @return Baseline wind object.
|
||||
*/
|
||||
public Wind generateBaselineWind() {
|
||||
return new Wind(windBaselineBearing, windBaselineSpeed);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a random Wind object, that is within the provided bounds.
|
||||
* @return Generated wind object.
|
||||
*/
|
||||
public Wind generateRandomWind() {
|
||||
|
||||
double windSpeed = generateRandomWindSpeed();
|
||||
Bearing windBearing = generateRandomWindBearing();
|
||||
|
||||
return new Wind(windBearing, windSpeed);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a random wind speed within the specified bounds. In knots.
|
||||
* @return Wind speed, in knots.
|
||||
*/
|
||||
private double generateRandomWindSpeed() {
|
||||
|
||||
double randomSpeedKnots = generateRandomValueInBounds(windSpeedLowerBound, windSpeedUpperBound);
|
||||
|
||||
return randomSpeedKnots;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Generates a random wind bearing within the specified bounds.
|
||||
* @return Wind bearing.
|
||||
*/
|
||||
private Bearing generateRandomWindBearing() {
|
||||
|
||||
double randomBearingDegrees = generateRandomValueInBounds(windBearingLowerBound.degrees(), windBearingUpperBound.degrees());
|
||||
|
||||
return Bearing.fromDegrees(randomBearingDegrees);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Generates a random value within a specified interval.
|
||||
* @param lowerBound The lower bound of the interval.
|
||||
* @param upperBound The upper bound of the interval.
|
||||
* @return A random value within the interval.
|
||||
*/
|
||||
private static double generateRandomValueInBounds(double lowerBound, double upperBound) {
|
||||
|
||||
float proportion = new Random().nextFloat();
|
||||
|
||||
double delta = upperBound - lowerBound;
|
||||
|
||||
double amount = delta * proportion;
|
||||
|
||||
double finalAmount = amount + lowerBound;
|
||||
|
||||
return finalAmount;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Generates a new value within an interval, given a start value, chance to change, and change amount.
|
||||
* @param lowerBound Lower bound of interval.
|
||||
* @param upperBound Upper bound of interval.
|
||||
* @param currentValue The current value to change.
|
||||
* @param changeAmount The amount to change by.
|
||||
* @param chanceToChange The change to actually change the value.
|
||||
* @return The new value.
|
||||
*/
|
||||
private static double generateNextValueInBounds(double lowerBound, double upperBound, double currentValue, double changeAmount, double chanceToChange) {
|
||||
|
||||
float chance = new Random().nextFloat();
|
||||
|
||||
|
||||
if (chance <= chanceToChange) {
|
||||
currentValue += changeAmount;
|
||||
|
||||
} else if (chance <= (2 * chanceToChange)) {
|
||||
currentValue -= changeAmount;
|
||||
|
||||
}
|
||||
|
||||
currentValue = clamp(lowerBound, upperBound, currentValue);
|
||||
|
||||
return currentValue;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Generates the next Wind object, that is within the provided bounds. This randomly increases or decreases the wind's speed and bearing.
|
||||
* @param currentWind The current wind to change. This is not modified.
|
||||
* @return Generated wind object.
|
||||
*/
|
||||
public Wind generateNextWind(Wind currentWind) {
|
||||
|
||||
double windSpeed = generateNextWindSpeed(currentWind.getWindSpeed());
|
||||
Bearing windBearing = generateNextWindBearing(currentWind.getWindDirection());
|
||||
|
||||
return new Wind(windBearing, windSpeed);
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Generates the next wind speed to use.
|
||||
* @param windSpeed Current wind speed, in knots.
|
||||
* @return Next wind speed, in knots.
|
||||
*/
|
||||
private double generateNextWindSpeed(double windSpeed) {
|
||||
|
||||
double chanceToChange = 0.2;
|
||||
double changeAmount = 0.1;
|
||||
|
||||
double nextWindSpeed = generateNextValueInBounds(
|
||||
windSpeedLowerBound,
|
||||
windSpeedUpperBound,
|
||||
windSpeed,
|
||||
changeAmount,
|
||||
chanceToChange);
|
||||
|
||||
return nextWindSpeed;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Generates the next wind speed to use.
|
||||
* @param windBearing Current wind bearing.
|
||||
* @return Next wind speed.
|
||||
*/
|
||||
private Bearing generateNextWindBearing(Bearing windBearing) {
|
||||
|
||||
double chanceToChange = 0.2;
|
||||
double changeAmount = 0.5;
|
||||
|
||||
double nextWindBearingDegrees = generateNextValueInBounds(
|
||||
windBearingLowerBound.degrees(),
|
||||
windBearingUpperBound.degrees(),
|
||||
windBearing.degrees(),
|
||||
changeAmount,
|
||||
chanceToChange);
|
||||
|
||||
return Bearing.fromDegrees(nextWindBearingDegrees);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Clamps a value to be within an interval.
|
||||
* @param lower Lower bound of the interval.
|
||||
* @param upper Upper bound of the interval.
|
||||
* @param value Value to clamp.
|
||||
* @return The clamped value.
|
||||
*/
|
||||
private static double clamp(double lower, double upper, double value) {
|
||||
|
||||
if (value > upper) {
|
||||
value = upper;
|
||||
|
||||
} else if (value < lower) {
|
||||
value = lower;
|
||||
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@ -0,0 +1,14 @@
|
||||
package mock.model.commandFactory;
|
||||
|
||||
import mock.model.MockBoat;
|
||||
import mock.model.MockRace;
|
||||
|
||||
/**
|
||||
* Allows RaceLogic to control MockRace state according to the Command pattern
|
||||
*/
|
||||
public interface Command {
|
||||
/**
|
||||
* Execute command - standard method name in pattern
|
||||
*/
|
||||
void execute();
|
||||
}
|
||||
@ -0,0 +1,27 @@
|
||||
package mock.model.commandFactory;
|
||||
|
||||
import mock.model.MockBoat;
|
||||
import mock.model.MockRace;
|
||||
import network.Messages.Enums.BoatActionEnum;
|
||||
|
||||
/**
|
||||
* Factory class for Command objects
|
||||
*/
|
||||
public class CommandFactory {
|
||||
/**
|
||||
* Generates a command on a race and boat corresponding to the protocol action number.
|
||||
* @param race to receive command
|
||||
* @param boat to receive command in race
|
||||
* @param action number to select command
|
||||
* @return
|
||||
*/
|
||||
public static Command createCommand(MockRace race, MockBoat boat, BoatActionEnum action) {
|
||||
switch(action) {
|
||||
case AUTO_PILOT: return new VMGCommand(race, boat);
|
||||
case TACK_GYBE: return new TackGybeCommand(race, boat);
|
||||
case UPWIND: return new WindCommand(race, boat, true);
|
||||
case DOWNWIND: return new WindCommand(race, boat, false);
|
||||
default: return null; // TODO - please please have discussion over what to default to
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,24 @@
|
||||
package mock.model.commandFactory;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Queue;
|
||||
|
||||
/**
|
||||
* Wraps multiple commands into a composite to execute queued commands during a frame.
|
||||
*/
|
||||
public class CompositeCommand implements Command {
|
||||
private Queue<Command> commands;
|
||||
|
||||
public CompositeCommand() {
|
||||
this.commands = new ArrayDeque<>();
|
||||
}
|
||||
|
||||
public void addCommand(Command command) {
|
||||
commands.add(command);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() {
|
||||
while(!commands.isEmpty()) commands.remove().execute();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,35 @@
|
||||
package mock.model.commandFactory;
|
||||
|
||||
import mock.model.MockBoat;
|
||||
import mock.model.MockRace;
|
||||
|
||||
/**
|
||||
* Created by David on 2/08/2017.
|
||||
*/
|
||||
public class TackGybeCommand implements Command {
|
||||
private MockRace race;
|
||||
private MockBoat boat;
|
||||
|
||||
public TackGybeCommand(MockRace race, MockBoat boat) {
|
||||
this.race = race;
|
||||
this.boat = boat;
|
||||
}
|
||||
|
||||
//The refactoring of MockRace will require changes to be made
|
||||
@Override
|
||||
public void execute() {
|
||||
|
||||
boat.setAutoVMG(false);
|
||||
|
||||
/*VMG newVMG = boat.getPolars().calculateVMG(
|
||||
race.getWindDirection(),
|
||||
race.getWindSpeed(),
|
||||
boat.calculateBearingToNextMarker(),
|
||||
Bearing.fromDegrees(0d),
|
||||
Bearing.fromDegrees(359.99999d));
|
||||
VMG boatVMG = new VMG(boat.getCurrentSpeed(), boat.getBearing());
|
||||
if(race.improvesVelocity(boatVMG, newVMG, boat.calculateBearingToNextMarker())){
|
||||
boat.setVMG(newVMG);
|
||||
}*/
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,30 @@
|
||||
package mock.model.commandFactory;
|
||||
|
||||
import mock.model.MockBoat;
|
||||
import mock.model.MockRace;
|
||||
|
||||
/**
|
||||
* Created by David on 2/08/2017.
|
||||
*/
|
||||
public class VMGCommand implements Command {
|
||||
private MockRace race;
|
||||
private MockBoat boat;
|
||||
|
||||
public VMGCommand(MockRace race, MockBoat boat) {
|
||||
this.race = race;
|
||||
this.boat = boat;
|
||||
}
|
||||
|
||||
//The refactoring of MockRace will require changes to be made
|
||||
@Override
|
||||
public void execute() {
|
||||
boat.setAutoVMG(true);
|
||||
/*VMG newVMG = boat.getPolars().calculateVMG(
|
||||
race.getWindDirection(),
|
||||
race.getWindSpeed(),
|
||||
boat.calculateBearingToNextMarker(),
|
||||
Bearing.fromDegrees(0d),
|
||||
Bearing.fromDegrees(359.99999d));
|
||||
boat.setVMG(newVMG);*/
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,37 @@
|
||||
package mock.model.commandFactory;
|
||||
|
||||
import mock.model.MockBoat;
|
||||
import mock.model.MockRace;
|
||||
import shared.model.Bearing;
|
||||
|
||||
/**
|
||||
* Created by connortaylorbrown on 4/08/17.
|
||||
*/
|
||||
public class WindCommand implements Command {
|
||||
private MockRace race;
|
||||
private MockBoat boat;
|
||||
private int direction;
|
||||
|
||||
public WindCommand(MockRace race, MockBoat boat, boolean upwind) {
|
||||
this.race = race;
|
||||
this.boat = boat;
|
||||
this.direction = upwind? -1 : 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() {
|
||||
|
||||
boat.setAutoVMG(false);
|
||||
|
||||
double wind = race.getWindDirection().degrees();
|
||||
double heading = boat.getBearing().degrees();
|
||||
|
||||
double offset = 3.0;
|
||||
|
||||
offset *= direction;
|
||||
double headWindDelta = wind - heading;
|
||||
if ((headWindDelta < 0) || (headWindDelta > 180)) offset *= -1;
|
||||
|
||||
boat.setBearing(Bearing.fromDegrees(heading + offset));
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,46 @@
|
||||
package network.MessageEncoders;
|
||||
|
||||
|
||||
import network.Exceptions.InvalidMessageException;
|
||||
import network.Messages.AC35Data;
|
||||
import network.Messages.RequestToJoin;
|
||||
import network.Utils.ByteConverter;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
* This encoder can encode a {@link network.Messages.RequestToJoin} message.
|
||||
*/
|
||||
public class RequestToJoinEncoder implements MessageEncoder {
|
||||
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public RequestToJoinEncoder() {
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public byte[] encode(AC35Data message) throws InvalidMessageException {
|
||||
|
||||
try {
|
||||
|
||||
//Downcast.
|
||||
RequestToJoin requestToJoin = (RequestToJoin) message;
|
||||
|
||||
|
||||
ByteBuffer requestToJoinBuffer = ByteBuffer.allocate(4);
|
||||
|
||||
requestToJoinBuffer.put(ByteConverter.intToBytes(requestToJoin.getRequestType().getValue(), 4));
|
||||
|
||||
byte[] result = requestToJoinBuffer.array();
|
||||
|
||||
return result;
|
||||
|
||||
} catch (Exception e) {
|
||||
throw new InvalidMessageException("Could not encode RequestToJoin message.", e);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,69 @@
|
||||
package network.MessageEncoders;
|
||||
|
||||
|
||||
import network.Exceptions.InvalidMessageException;
|
||||
import network.Messages.AC35Data;
|
||||
import network.Messages.XMLMessage;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import static network.Utils.ByteConverter.intToBytes;
|
||||
import static network.Utils.ByteConverter.longToBytes;
|
||||
|
||||
/**
|
||||
* This encoder can encode a {@link XMLMessage} message.
|
||||
*/
|
||||
public class XMLMessageEncoder implements MessageEncoder {
|
||||
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public XMLMessageEncoder() {
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public byte[] encode(AC35Data message) throws InvalidMessageException {
|
||||
|
||||
try {
|
||||
|
||||
//Downcast.
|
||||
XMLMessage xmlMessage = (XMLMessage) message;
|
||||
|
||||
|
||||
byte[] messageBytes = xmlMessage.getXmlMessage().getBytes(StandardCharsets.UTF_8);
|
||||
|
||||
//Message is 14 + xmlMessage.length bytes.
|
||||
ByteBuffer tempOutputByteBuffer = ByteBuffer.allocate(14 + messageBytes.length);
|
||||
|
||||
//ackNumber converted to bytes
|
||||
byte[] ackNumberBytes = intToBytes(xmlMessage.getAckNumber(), 2);
|
||||
|
||||
//Timestamp converted to bytes.
|
||||
byte[] timestampBytes = longToBytes(xmlMessage.getTimeStamp(), 6);
|
||||
|
||||
//sequenceNumber converted to bytes
|
||||
byte[] sequenceNumberBytes = intToBytes(xmlMessage.getSequenceNumber(), 2);
|
||||
|
||||
//xmlMsgLength converted to bytes
|
||||
byte[] xmlMsgLengthBytes = intToBytes(xmlMessage.getXmlMsgLength(), 2);
|
||||
|
||||
|
||||
tempOutputByteBuffer.put(xmlMessage.getVersionNumber());
|
||||
tempOutputByteBuffer.put(ackNumberBytes);
|
||||
tempOutputByteBuffer.put(timestampBytes);
|
||||
tempOutputByteBuffer.put(xmlMessage.getXmlMsgSubType().getValue());
|
||||
tempOutputByteBuffer.put(sequenceNumberBytes);
|
||||
tempOutputByteBuffer.put(xmlMsgLengthBytes);
|
||||
tempOutputByteBuffer.put(messageBytes);
|
||||
|
||||
return tempOutputByteBuffer.array();
|
||||
|
||||
} catch (Exception e) {
|
||||
throw new InvalidMessageException("Could not encode XMLMessage message.", e);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@ -0,0 +1,51 @@
|
||||
package shared.model;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* This class encapsulates the wind during a race.
|
||||
* It has speed and a bearing.
|
||||
* This is intended to be immutable.
|
||||
*/
|
||||
public class Wind {
|
||||
|
||||
/**
|
||||
* The current wind direction bearing.
|
||||
*/
|
||||
private Bearing windDirection;
|
||||
|
||||
/**
|
||||
* Wind speed (knots).
|
||||
* Convert this to millimeters per second before passing to RaceStatus.
|
||||
*/
|
||||
private double windSpeed;
|
||||
|
||||
|
||||
/**
|
||||
* Constructs a new wind object, with a given direction and speed, in knots.
|
||||
* @param windDirection The direction of the wind.
|
||||
* @param windSpeed The speed of the wind, in knots.
|
||||
*/
|
||||
public Wind(Bearing windDirection, double windSpeed) {
|
||||
this.windDirection = windDirection;
|
||||
this.windSpeed = windSpeed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the race wind's bearing.
|
||||
* @return The race wind's bearing.
|
||||
*/
|
||||
public Bearing getWindDirection() {
|
||||
return windDirection;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the race wind's speed, in knots.
|
||||
* @return The race wind's speed, in knots.
|
||||
*/
|
||||
public double getWindSpeed() {
|
||||
return windSpeed;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,152 @@
|
||||
package visualiser.Controllers;
|
||||
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.property.Property;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.Pane;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.shape.Circle;
|
||||
import shared.model.Bearing;
|
||||
import shared.model.Wind;
|
||||
import visualiser.model.VisualiserRace;
|
||||
|
||||
/**
|
||||
* Controller for the arrow.fxml view.
|
||||
*/
|
||||
public class ArrowController {
|
||||
|
||||
|
||||
@FXML
|
||||
private Pane compass;
|
||||
|
||||
@FXML
|
||||
private StackPane arrowStackPane;
|
||||
|
||||
@FXML
|
||||
private ImageView arrowImage;
|
||||
|
||||
@FXML
|
||||
private Circle circle;
|
||||
|
||||
@FXML
|
||||
private Label northLabel;
|
||||
|
||||
@FXML
|
||||
private Label windLabel;
|
||||
|
||||
@FXML
|
||||
private Label speedLabel;
|
||||
|
||||
|
||||
/**
|
||||
* This is the property our arrow control binds to.
|
||||
*/
|
||||
private Property<Wind> wind;
|
||||
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public ArrowController() {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets which wind property the arrow control should bind to.
|
||||
* @param wind The wind property to bind to.
|
||||
*/
|
||||
public void setWindProperty(Property<Wind> wind) {
|
||||
this.wind = wind;
|
||||
|
||||
wind.addListener((observable, oldValue, newValue) -> {
|
||||
if (newValue != null) {
|
||||
Platform.runLater(() -> updateWind(newValue));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Updates the control to use the new wind value.
|
||||
* This updates the arrow direction (due to bearing), arrow length (due to speed), and label (due to speed).
|
||||
* @param wind The wind value to use.
|
||||
*/
|
||||
private void updateWind(Wind wind) {
|
||||
updateWindBearing(wind.getWindDirection());
|
||||
updateWindSpeed(wind.getWindSpeed());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Updates the control to account for the new wind speed.
|
||||
* This changes the length (height) of the wind arrow, and updates the speed label.
|
||||
* @param speedKnots The new wind speed, in knots.
|
||||
*/
|
||||
private void updateWindSpeed(double speedKnots) {
|
||||
updateWindArrowLength(speedKnots);
|
||||
updateWindSpeedLabel(speedKnots);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the length of the wind arrow according to the specified wind speed.
|
||||
* @param speedKnots Wind speed, in knots.
|
||||
*/
|
||||
private void updateWindArrowLength(double speedKnots) {
|
||||
|
||||
//At 2 knots, the arrow reaches its minimum height, and at 30 knots it reaches its maximum height.
|
||||
double minKnots = 2;
|
||||
double maxKnots = 30;
|
||||
double deltaKnots = maxKnots - minKnots;
|
||||
|
||||
double minHeight = 25;
|
||||
double maxHeight = 75;
|
||||
double deltaHeight = maxHeight - minHeight;
|
||||
|
||||
//Clamp speed.
|
||||
if (speedKnots > maxKnots) {
|
||||
speedKnots = maxKnots;
|
||||
} else if (speedKnots < minKnots) {
|
||||
speedKnots = minKnots;
|
||||
}
|
||||
|
||||
//How far between the knots bounds is the current speed?
|
||||
double currentDeltaKnots = speedKnots - minKnots;
|
||||
double currentKnotsScalar = currentDeltaKnots / deltaKnots;
|
||||
|
||||
//Thus, how far between the pixel height bounds should the arrow height be?
|
||||
double newHeight = minHeight + (currentKnotsScalar * deltaHeight);
|
||||
|
||||
arrowImage.setFitHeight(newHeight);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the wind speed label according to the specified wind speed.
|
||||
* @param speedKnots Wind speed, in knots.
|
||||
*/
|
||||
private void updateWindSpeedLabel(double speedKnots) {
|
||||
speedLabel.setText(String.format("%.1fkn", speedKnots));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Updates the control to account for a new wind bearing.
|
||||
* This rotates the arrow according to the bearing.
|
||||
* @param bearing The bearing to use to rotate arrow.
|
||||
*/
|
||||
private void updateWindBearing(Bearing bearing) {
|
||||
|
||||
//We need to display wind-from, so add 180 degrees.
|
||||
Bearing fromBearing = Bearing.fromDegrees(bearing.degrees() + 180d);
|
||||
|
||||
//Rotate the wind arrow.
|
||||
arrowStackPane.setRotate(fromBearing.degrees());
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
After Width: | Height: | Size: 23 KiB |
@ -1,34 +1,58 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import javafx.scene.paint.*?>
|
||||
<?import javafx.scene.text.*?>
|
||||
<?import javafx.scene.control.*?>
|
||||
<?import javafx.scene.shape.*?>
|
||||
<?import javafx.scene.image.*?>
|
||||
<?import java.lang.*?>
|
||||
<?import javafx.scene.layout.*?>
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.image.Image?>
|
||||
<?import javafx.scene.image.ImageView?>
|
||||
<?import javafx.scene.layout.ColumnConstraints?>
|
||||
<?import javafx.scene.layout.GridPane?>
|
||||
<?import javafx.scene.layout.Pane?>
|
||||
<?import javafx.scene.layout.RowConstraints?>
|
||||
<?import javafx.scene.layout.StackPane?>
|
||||
<?import javafx.scene.shape.Circle?>
|
||||
<?import javafx.scene.text.Font?>
|
||||
|
||||
<Pane fx:id="compass" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="125.0" prefWidth="125.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1">
|
||||
|
||||
<GridPane fx:id="arrowGridPane" maxHeight="-Infinity" maxWidth="-Infinity" xmlns="http://javafx.com/javafx/8.0.111" xmlns:fx="http://javafx.com/fxml/1" fx:controller="visualiser.Controllers.ArrowController">
|
||||
<columnConstraints>
|
||||
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" />
|
||||
</columnConstraints>
|
||||
<rowConstraints>
|
||||
<RowConstraints minHeight="10.0" vgrow="SOMETIMES" />
|
||||
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
|
||||
</rowConstraints>
|
||||
<children>
|
||||
<StackPane fx:id="arrow" prefHeight="125.0" prefWidth="125.0">
|
||||
<Pane fx:id="compass" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="125.0" prefWidth="125.0">
|
||||
<children>
|
||||
<ImageView fitHeight="75.0" fitWidth="75.0">
|
||||
<image>
|
||||
<Image url="@../images/arrow.png" />
|
||||
</image>
|
||||
</ImageView>
|
||||
<StackPane fx:id="arrowStackPane" prefHeight="125.0" prefWidth="125.0">
|
||||
<children>
|
||||
<ImageView fx:id="arrowImage" fitHeight="75.0" fitWidth="75.0">
|
||||
<image>
|
||||
<Image url="@../images/arrow.png" />
|
||||
</image>
|
||||
</ImageView>
|
||||
</children>
|
||||
</StackPane>
|
||||
<Circle fx:id="circle" fill="#1f93ff00" layoutX="63.0" layoutY="63.0" radius="60.0" stroke="BLACK" strokeType="INSIDE" strokeWidth="3.0" />
|
||||
<Label fx:id="northLabel" layoutX="55.0" layoutY="1.0" text="N">
|
||||
<font>
|
||||
<Font name="System Bold" size="18.0" />
|
||||
</font>
|
||||
</Label>
|
||||
<Label fx:id="windLabel" layoutX="42.0" layoutY="95.0" text="Wind">
|
||||
<font>
|
||||
<Font name="System Bold" size="16.0" />
|
||||
</font>
|
||||
</Label>
|
||||
</children>
|
||||
</StackPane>
|
||||
<Circle fill="#1f93ff00" layoutX="63.0" layoutY="63.0" radius="60.0" stroke="BLACK" strokeType="INSIDE" strokeWidth="3.0" />
|
||||
<Label layoutX="55.0" layoutY="1.0" text="N">
|
||||
<font>
|
||||
<Font name="System Bold" size="18.0" />
|
||||
</font>
|
||||
</Label>
|
||||
<Label layoutX="42.0" layoutY="99.0" text="Wind">
|
||||
</Pane>
|
||||
<Label fx:id="speedLabel" text="SPEED" GridPane.halignment="CENTER" GridPane.hgrow="NEVER" GridPane.rowIndex="1">
|
||||
<font>
|
||||
<Font name="System Bold" size="16.0" />
|
||||
</font>
|
||||
<GridPane.margin>
|
||||
<Insets />
|
||||
</GridPane.margin>
|
||||
</Label>
|
||||
</children>
|
||||
</Pane>
|
||||
</GridPane>
|
||||
|
||||
@ -0,0 +1,111 @@
|
||||
package mock.model;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import shared.model.Bearing;
|
||||
import shared.model.Wind;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
|
||||
public class WindGeneratorTest {
|
||||
|
||||
|
||||
private WindGenerator windGenerator;
|
||||
|
||||
private Bearing windBaselineBearing;
|
||||
private Bearing windBearingLowerBound;
|
||||
private Bearing windBearingUpperBound;
|
||||
private double windBaselineSpeed;
|
||||
private double windSpeedLowerBound;
|
||||
private double windSpeedUpperBound;
|
||||
|
||||
private double speedKnotsEpsilon;
|
||||
private double bearingDegreeEpsilon;
|
||||
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
|
||||
|
||||
//Bounds.
|
||||
this.windBaselineBearing = Bearing.fromDegrees(88.3);
|
||||
this.windBearingLowerBound = Bearing.fromDegrees(66.5);
|
||||
this.windBearingUpperBound = Bearing.fromDegrees(248.6);
|
||||
this.windBaselineSpeed = 13;
|
||||
this.windSpeedLowerBound = 7;
|
||||
this.windSpeedUpperBound = 20;
|
||||
|
||||
this.windGenerator = new WindGenerator(
|
||||
windBaselineBearing,
|
||||
windBearingLowerBound,
|
||||
windBearingUpperBound,
|
||||
windBaselineSpeed,
|
||||
windSpeedLowerBound,
|
||||
windSpeedUpperBound );
|
||||
|
||||
this.bearingDegreeEpsilon = 0.001;
|
||||
this.speedKnotsEpsilon = 0.001;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Tests if the baseline wind generated it accurate.
|
||||
*/
|
||||
@Test
|
||||
public void generateBaselineWindTest() {
|
||||
|
||||
Wind wind = windGenerator.generateBaselineWind();
|
||||
|
||||
assertEquals(windBaselineSpeed, wind.getWindSpeed(), speedKnotsEpsilon);
|
||||
assertEquals(windBaselineBearing.degrees(), wind.getWindDirection().degrees(), bearingDegreeEpsilon);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if the random wind generated is inside the bounds.
|
||||
*/
|
||||
@Test
|
||||
public void generateRandomWindTest() {
|
||||
|
||||
int randomWindCount = 1000;
|
||||
|
||||
for (int i = 0; i < randomWindCount; i++) {
|
||||
|
||||
Wind wind = windGenerator.generateRandomWind();
|
||||
|
||||
assertTrue(wind.getWindSpeed() >= windSpeedLowerBound);
|
||||
assertTrue(wind.getWindSpeed() <= windSpeedUpperBound);
|
||||
|
||||
assertTrue(wind.getWindDirection().degrees() >= windBearingLowerBound.degrees());
|
||||
assertTrue(wind.getWindDirection().degrees() <= windBearingUpperBound.degrees());
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Tests if the next wind generated is inside the bounds.
|
||||
*/
|
||||
@Test
|
||||
public void generateNextWindTest() {
|
||||
|
||||
Wind wind = windGenerator.generateBaselineWind();
|
||||
|
||||
int randomWindCount = 1000;
|
||||
|
||||
for (int i = 0; i < randomWindCount; i++) {
|
||||
|
||||
wind = windGenerator.generateNextWind(wind);
|
||||
|
||||
assertTrue(wind.getWindSpeed() >= windSpeedLowerBound);
|
||||
assertTrue(wind.getWindSpeed() <= windSpeedUpperBound);
|
||||
|
||||
assertTrue(wind.getWindDirection().degrees() >= windBearingLowerBound.degrees());
|
||||
assertTrue(wind.getWindDirection().degrees() <= windBearingUpperBound.degrees());
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,58 @@
|
||||
package mock.model.commandFactory;
|
||||
|
||||
import mock.model.MockBoat;
|
||||
import mock.model.MockRace;
|
||||
import network.Messages.Enums.BoatActionEnum;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mockito.Mock;
|
||||
import shared.model.Bearing;
|
||||
import shared.model.Boat;
|
||||
import shared.model.Race;
|
||||
import visualiser.model.VisualiserRace;
|
||||
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.testng.Assert.*;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Created by connortaylorbrown on 4/08/17.
|
||||
*/
|
||||
public class WindCommandTest {
|
||||
private MockRace race;
|
||||
private MockBoat boat;
|
||||
private Command upwind;
|
||||
private Command downwind;
|
||||
private double initial;
|
||||
|
||||
private double offset = 3.0;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
race = mock(MockRace.class);
|
||||
boat = new MockBoat(0, "Bob", "NZ", null);
|
||||
|
||||
when(race.getWindDirection()).thenReturn(Bearing.fromDegrees(0.0));
|
||||
boat.setBearing(Bearing.fromDegrees(45.0));
|
||||
|
||||
upwind = CommandFactory.createCommand(race, boat, BoatActionEnum.UPWIND);
|
||||
downwind = CommandFactory.createCommand(race, boat, BoatActionEnum.DOWNWIND);
|
||||
|
||||
initial = boat.getBearing().degrees();
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure the difference between initial and final angle is 3 degrees
|
||||
*/
|
||||
@Test
|
||||
public void upwindCommandDecreasesAngle() {
|
||||
upwind.execute();
|
||||
assertEquals(initial - boat.getBearing().degrees(), -offset, 1e-5);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void downwindCommandIncreasesAngle() {
|
||||
downwind.execute();
|
||||
assertEquals(initial - boat.getBearing().degrees(), offset, 1e-5);
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@ -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());
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue