Merge remote-tracking branch 'refs/remotes/origin/44-als-gebruiker-wil-ik-dat-ik-feedback-krijg-als-mijn-bewegingen-niet-optimaal-zijn-zodat-ik-weet'
# Conflicts: # code/src/Fitbot/app/src/main/java/com/example/fitbot/MainActivity.java # code/src/Fitbot/app/src/main/java/com/example/fitbot/MainScreen.java
This commit is contained in:
@@ -7,7 +7,7 @@ android {
|
||||
|
||||
defaultConfig {
|
||||
applicationId "com.example.fitbot"
|
||||
minSdk 23
|
||||
minSdk 29
|
||||
targetSdk 29
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
|
@@ -14,7 +14,7 @@ import com.aldebaran.qi.sdk.object.conversation.Say;
|
||||
import com.aldebaran.qi.sdk.object.locale.Language;
|
||||
import com.aldebaran.qi.sdk.object.locale.Locale;
|
||||
import com.aldebaran.qi.sdk.object.locale.Region;
|
||||
//import com.example.fitbot.ui.SportMenuActivity;
|
||||
import com.example.fitbot.ui.SportMenuActivity;
|
||||
|
||||
public class MainActivity extends RobotActivity implements RobotLifecycleCallbacks {
|
||||
|
||||
@@ -22,10 +22,10 @@ public class MainActivity extends RobotActivity implements RobotLifecycleCallbac
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
// Register the RobotLifecycleCallbacks to this Activity.
|
||||
// Button button = findViewById(R.id.menu_switch_btn);
|
||||
// button.setOnClickListener(v -> {
|
||||
// startActivity(new Intent(MainActivity.this, SportMenuActivity.class));
|
||||
// });
|
||||
Button button = findViewById(R.id.menu_switch_btn);
|
||||
button.setOnClickListener(v -> {
|
||||
startActivity(new Intent(MainActivity.this, SportMenuActivity.class));
|
||||
});
|
||||
QiSDK.register(this, this);
|
||||
|
||||
}
|
||||
|
@@ -11,7 +11,7 @@ import android.support.v7.app.AppCompatActivity;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.widget.Button;
|
||||
|
||||
//import com.example.fitbot.ui.SportMenuActivity;
|
||||
import com.example.fitbot.ui.SportMenuActivity;
|
||||
|
||||
public class MainScreen extends AppCompatActivity {
|
||||
|
||||
|
@@ -1,55 +0,0 @@
|
||||
package com.example.fitbot.processing;
|
||||
|
||||
public class MotionData {
|
||||
|
||||
// Data of the motion sensor
|
||||
public float accelerationX;
|
||||
public float accelerationY;
|
||||
public float accelerationZ;
|
||||
public float rotationX;
|
||||
public float rotationY;
|
||||
public float rotationZ;
|
||||
|
||||
// Delimiter for the data received from the motion sensor
|
||||
private static final String DATA_DELIMITER = ";";
|
||||
|
||||
/**
|
||||
* Constructor for the MotionData class.
|
||||
*
|
||||
* @param accelerationX The acceleration in the X axis in m/s^2.
|
||||
* @param accelerationY The acceleration in the Y axis in m/s^2.
|
||||
* @param accelerationZ The acceleration in the Z axis in m/s^2.
|
||||
* @param rotationX The rotation in the X axis in degrees.
|
||||
* @param rotationY The rotation in the Y axis in degrees.
|
||||
* @param rotationZ The rotation in the Z axis in degrees.
|
||||
*/
|
||||
public MotionData(float accelerationX, float accelerationY, float accelerationZ, float rotationX, float rotationY, float rotationZ) {
|
||||
this.accelerationX = accelerationX;
|
||||
this.accelerationY = accelerationY;
|
||||
this.accelerationZ = accelerationZ;
|
||||
this.rotationX = rotationX;
|
||||
this.rotationY = rotationY;
|
||||
this.rotationZ = rotationZ;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function for decoding a string into a MotionData object.
|
||||
* This string must contain the data of the motion sensor
|
||||
* separated by the delimiter. (;)
|
||||
*
|
||||
* @param data The string containing the data of the motion sensor.
|
||||
* @return An instance of MotionData.
|
||||
*/
|
||||
public static MotionData decode(String data) {
|
||||
String[] parts = data.split(DATA_DELIMITER);
|
||||
return new MotionData(
|
||||
Float.parseFloat(parts[0]),
|
||||
Float.parseFloat(parts[1]),
|
||||
Float.parseFloat(parts[2]),
|
||||
Float.parseFloat(parts[3]),
|
||||
Float.parseFloat(parts[4]),
|
||||
Float.parseFloat(parts[5])
|
||||
);
|
||||
}
|
||||
|
||||
}
|
@@ -1,31 +0,0 @@
|
||||
package com.example.fitbot.processing;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* Class representing the input stream of the motion sensor.
|
||||
* This class will be responsible for reading the data from
|
||||
* the motion sensor and processing it, by creating a
|
||||
* server and starting a WebSocket connection with the ESP32.
|
||||
*/
|
||||
public class MotionInputStream extends InputStream {
|
||||
|
||||
/**
|
||||
* Function for starting the listening process
|
||||
* of the motion sensor. This function will
|
||||
* @return An instance of MotionInputStream.
|
||||
*/
|
||||
public static MotionInputStream startListening()
|
||||
{
|
||||
// Create server
|
||||
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int read() {
|
||||
return 0;
|
||||
}
|
||||
}
|
@@ -1,4 +0,0 @@
|
||||
package com.example.fitbot.processing;
|
||||
|
||||
public class MotionProcessor {
|
||||
}
|
@@ -0,0 +1,104 @@
|
||||
package com.example.fitbot.util.processing;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class GesturePath {
|
||||
|
||||
// The vectors that make up the path.
|
||||
private final Vector3[] vectors;
|
||||
|
||||
public GesturePath(Vector3[] vectors) {
|
||||
this.vectors = vectors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the error between an arbitrary path segment and the reference point.
|
||||
*
|
||||
* @param referencePoint The reference point to calculate the error of.
|
||||
* @return The error offset between the path and the reference point.
|
||||
*/
|
||||
public double getError(Vector3 referencePoint) {
|
||||
// If there are no vectors, return 0.
|
||||
if ( vectors.length == 0)
|
||||
return 0;
|
||||
|
||||
// If there's only one vector, return the distance to that vector.
|
||||
if ( vectors.length == 1)
|
||||
return vectors[0].distance(referencePoint);
|
||||
|
||||
double distance = Double.MAX_VALUE;
|
||||
double currentDistSq, nextDistSq;
|
||||
int closestVectorIdx = 0;
|
||||
|
||||
// Acquire two closest points to the reference point.
|
||||
for ( int i = 0; i < vectors.length - 1; i++) {
|
||||
currentDistSq = vectors[i].distanceSq(referencePoint);
|
||||
nextDistSq = vectors[i + 1].distanceSq(referencePoint);
|
||||
|
||||
if ( currentDistSq < distance) {
|
||||
distance = currentDistSq;
|
||||
closestVectorIdx = i;
|
||||
} else if ( nextDistSq < distance) {
|
||||
distance = nextDistSq;
|
||||
closestVectorIdx = i + 1;
|
||||
i++; // Skip the next iteration; this point is already closer.
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate the error between the two closest points.
|
||||
Vector3 pointB = (closestVectorIdx == vectors.length - 1) ?
|
||||
vectors[closestVectorIdx - 1] : // If the closest point is the last point, use the 1 to last one
|
||||
(closestVectorIdx > 0 && // Find the closer point between the surrounding points.
|
||||
(vectors[closestVectorIdx - 1].distanceSq(referencePoint) < vectors[closestVectorIdx + 1].distanceSq(referencePoint))) ?
|
||||
vectors[closestVectorIdx - 1] :
|
||||
vectors[closestVectorIdx + 1];
|
||||
|
||||
return referencePoint.distanceToLine(vectors[closestVectorIdx], pointB);
|
||||
}
|
||||
|
||||
// Builder class for the GesturePath object.
|
||||
public static class Builder {
|
||||
|
||||
// List of vectors to add to the GesturePath object.
|
||||
private final List<Vector3> vectors;
|
||||
|
||||
/**
|
||||
* Constructor for the Builder object.
|
||||
*
|
||||
* @param vectors The list of vectors to add.
|
||||
*/
|
||||
public Builder(List<Vector3> vectors) {
|
||||
this.vectors = vectors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Default constructor for the Builder object.
|
||||
*/
|
||||
public Builder() {
|
||||
this.vectors = new ArrayList<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a vector to the GesturePath object.
|
||||
*
|
||||
* @param vector The vector to add.
|
||||
* @return The Builder object.
|
||||
*/
|
||||
public Builder addVector(Vector3 vector) {
|
||||
vectors.add(vector);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the GesturePath object.
|
||||
*
|
||||
* @return The GesturePath object.
|
||||
*/
|
||||
public GesturePath build() {
|
||||
return new GesturePath(vectors.toArray(new Vector3[0]));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,63 @@
|
||||
package com.example.fitbot.util.processing;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public class MotionData {
|
||||
|
||||
// Data of the motion sensor
|
||||
public Vector3 acceleration, rotation;
|
||||
|
||||
// Delimiter for the data received from the motion sensor
|
||||
private static final String DATA_DELIMITER = ";";
|
||||
|
||||
/**
|
||||
* Constructor for the MotionData class.
|
||||
*
|
||||
* @param accelerationX The acceleration in the X axis in m/s^2.
|
||||
* @param accelerationY The acceleration in the Y axis in m/s^2.
|
||||
* @param accelerationZ The acceleration in the Z axis in m/s^2.
|
||||
* @param rotationX The rotation in the X axis in degrees.
|
||||
* @param rotationY The rotation in the Y axis in degrees.
|
||||
* @param rotationZ The rotation in the Z axis in degrees.
|
||||
*/
|
||||
public MotionData(double accelerationX, double accelerationY, double accelerationZ, double rotationX, double rotationY, double rotationZ) {
|
||||
this.acceleration = new Vector3(accelerationX, accelerationY, accelerationZ);
|
||||
this.rotation = new Vector3(rotationX, rotationY, rotationZ);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor for the MotionData class.
|
||||
*
|
||||
* @param acceleration The acceleration vector in m/s^2.
|
||||
* @param rotation The rotation vector in degrees.
|
||||
*/
|
||||
public MotionData(Vector3 acceleration, Vector3 rotation) {
|
||||
this.acceleration = acceleration;
|
||||
this.rotation = rotation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function for decoding a string into a MotionData object.
|
||||
* This string must contain the data of the motion sensor
|
||||
* separated by the delimiter. (;)
|
||||
*
|
||||
* @param data The string containing the data of the motion sensor.
|
||||
* @return An instance of MotionData.
|
||||
*/
|
||||
public static MotionData decode(String data) {
|
||||
Objects.requireNonNull(data); // Ensure data is not null
|
||||
|
||||
String[] parts = data.split(DATA_DELIMITER);
|
||||
if (parts.length != 6)
|
||||
return null;
|
||||
|
||||
return new MotionData(
|
||||
Double.parseDouble(parts[0]),
|
||||
Double.parseDouble(parts[1]),
|
||||
Double.parseDouble(parts[2]),
|
||||
Double.parseDouble(parts[3]),
|
||||
Double.parseDouble(parts[4]),
|
||||
Double.parseDouble(parts[5])
|
||||
);
|
||||
}
|
||||
}
|
@@ -0,0 +1,246 @@
|
||||
package com.example.fitbot.util.processing;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.example.fitbot.util.server.IWebSocketHandler;
|
||||
import com.example.fitbot.util.server.WebSocket;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class MotionProcessor {
|
||||
|
||||
public static final String DELIMITER = ";";
|
||||
|
||||
private final List<MotionData> preprocessedData = new ArrayList<>(); // Preprocessed motion data
|
||||
private final List<Vector3> relativePath = new ArrayList<>(); // Relative path of the motion data
|
||||
private Vector3 ZERO = new Vector3(0, 0, 0);
|
||||
private double sampleRate = 1.0D; // samples/second
|
||||
private Consumer<Vector3> motionDataConsumer = (data) -> {};
|
||||
private GesturePath path;
|
||||
private WebSocket socket;
|
||||
|
||||
public MotionProcessor() {}
|
||||
|
||||
|
||||
/**
|
||||
* Function for starting the listening process
|
||||
* of the motion sensor. This function will create
|
||||
* a new WebSocket server and start listening for
|
||||
* incoming connections.
|
||||
*/
|
||||
public void startListening() {
|
||||
// Create socket server
|
||||
this.socket = WebSocket.createServer();
|
||||
|
||||
Log.i("MotionProcessor", "Listening for incoming connections.");
|
||||
|
||||
// Check if the socket
|
||||
if (socket != null) {
|
||||
// Update event handler to match our functionality.
|
||||
socket.setEventHandler(new IWebSocketHandler() {
|
||||
@Override
|
||||
public void onMessageReceived(WebSocket.Message message, WebSocket.MessageReply replier) {
|
||||
parsePacket(message.message);
|
||||
}
|
||||
});
|
||||
socket.startListening();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Function for stopping the listening process
|
||||
* of the motion sensor. This function will stop
|
||||
* the WebSocket server.
|
||||
*/
|
||||
public void stopListening() {
|
||||
if (socket != null) {
|
||||
socket.stop();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Function for parsing arbitrary packet data.
|
||||
* @param data The data to parse.
|
||||
*/
|
||||
public void parsePacket(@NotNull String data) {
|
||||
// If the message starts with 'data', it's a data packet.
|
||||
if ( data.startsWith("data")) {
|
||||
Log.i("MotionProcessor", "Received data packet: " + data.split(" ")[1]);
|
||||
MotionData parsedData = MotionData.decode(data.split(" ")[1]);
|
||||
if (parsedData != null) {
|
||||
addMotionData(parsedData);
|
||||
}
|
||||
// Otherwise check if it starts with 'calibrate', this is the ZERO point.
|
||||
} else if ( data.startsWith("zero")) { // message to calibrate device
|
||||
String[] vectorData = data.split(" ")[1].split(DELIMITER);
|
||||
ZERO = new Vector3(
|
||||
Float.parseFloat(vectorData[0]),
|
||||
Float.parseFloat(vectorData[1]),
|
||||
Float.parseFloat(vectorData[2])
|
||||
);
|
||||
Log.i("MotionProcessor", "Device calibrated at " + ZERO.toString());
|
||||
} else if ( data.startsWith("sampleRate")) {
|
||||
this.sampleRate = Double.parseDouble(data.split(" ")[1]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Function for setting the gesture path of the processor.
|
||||
*
|
||||
* @param path The path to set.
|
||||
*/
|
||||
public void setGesturePath(GesturePath path) {
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function for adding motion data to the processor.
|
||||
*
|
||||
* @param data The motion data to add.
|
||||
*/
|
||||
public void addMotionData(MotionData data) {
|
||||
preprocessedData.add(data);
|
||||
Vector3 previous = this.relativePath.isEmpty() ? ZERO : this.relativePath.get(this.relativePath.size() - 1);
|
||||
Vector3 relativeVector = getRelativeVector(data).add(previous);
|
||||
this.relativePath.add(relativeVector);
|
||||
motionDataConsumer.accept(relativeVector);
|
||||
}
|
||||
|
||||
/**
|
||||
* Function for updating the relative path.
|
||||
*
|
||||
* @param relativePath The new relative path.
|
||||
*/
|
||||
public void setRelativePath(List<Vector3> relativePath) {
|
||||
this.relativePath.clear();
|
||||
this.relativePath.addAll(relativePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Function for setting the motion data receiver.
|
||||
* @param consumer The consumer to set.
|
||||
*/
|
||||
public void setMotionDataEventHandler(Consumer<Vector3> consumer) {
|
||||
if ( consumer != null)
|
||||
this.motionDataConsumer = consumer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function for getting the relative vector of the motion data.
|
||||
* This function will calculate the relative position of the motion data
|
||||
* based on its acceleration and rotation vectors. This has to be done since
|
||||
* the acceleration vector is relative to its own rotation vector.
|
||||
*
|
||||
* @param motionData The motion data to calculate the relative vector for.
|
||||
* @return The relative vector of the motion data.
|
||||
*/
|
||||
public Vector3 getRelativeVector(MotionData motionData) {
|
||||
|
||||
// Rotate the acceleration vector back by the rotation vector to make it
|
||||
// perpendicular to the gravity vector, then apply double integration to get the relative position.
|
||||
// s = 1/2 * a * t^2
|
||||
return motionData.acceleration
|
||||
.rotate(motionData.rotation.negate())
|
||||
.divide(2)
|
||||
.multiply(sampleRate * sampleRate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Function for getting the error offsets of the provided path and the
|
||||
* received motion data.
|
||||
*
|
||||
* @param referencePath The reference path to compare the motion data to.
|
||||
* @return A list of error offsets of the motion data compared to the reference path.
|
||||
*/
|
||||
public List<Double> getErrors(GesturePath referencePath) {
|
||||
|
||||
// Return the errors of the relative path compared to the reference path.
|
||||
return relativePath
|
||||
.stream()
|
||||
.map(referencePath::getError)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Function for getting the error offsets of the motion data compared to the
|
||||
* reference path.
|
||||
*
|
||||
* @return A list of error offsets of the motion data compared to the reference path.
|
||||
* If no path is set, an empty list will be returned.
|
||||
*/
|
||||
public List<Double> getErrors() {
|
||||
if ( path == null)
|
||||
return new ArrayList<>();
|
||||
return getErrors(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Function for getting the error of the motion data compared to the reference path.
|
||||
*
|
||||
* @param path The path to compare the motion data to.
|
||||
* @param referencePoint The reference point to compare the motion data to.
|
||||
* @return The error of the motion data compared to the reference path.
|
||||
*/
|
||||
public double getError(GesturePath path, Vector3 referencePoint)
|
||||
{
|
||||
return path.getError(referencePoint);
|
||||
}
|
||||
|
||||
/**
|
||||
* Function for getting the error of the provided vector and the set path.
|
||||
* If no path is set, the error will be 0.
|
||||
*
|
||||
* @param referencePoint The reference point to compare the path data to.
|
||||
* @return The error of the motion data compared to the reference path.
|
||||
*/
|
||||
public double getError(Vector3 referencePoint) {
|
||||
if ( path == null)
|
||||
return 0;
|
||||
return path.getError(referencePoint);
|
||||
}
|
||||
|
||||
/**
|
||||
* Function for calculating the average error of the motion data
|
||||
* compared to the reference path.
|
||||
*
|
||||
* @param referencePath The reference path to compare the motion data to.
|
||||
* @return The average error of the motion data compared to the reference path.
|
||||
*/
|
||||
public double getAverageError(GesturePath referencePath) {
|
||||
return getErrors(referencePath)
|
||||
.stream()
|
||||
.mapToDouble(Double::doubleValue)
|
||||
.average()
|
||||
.orElse(0.0D);
|
||||
}
|
||||
|
||||
/**
|
||||
* Function for calculating the average error of the motion data
|
||||
* compared to the reference path.
|
||||
*
|
||||
* @return The average error of the motion data compared to the reference path.
|
||||
*/
|
||||
public double getAverageError() {
|
||||
if ( path == null)
|
||||
return 0;
|
||||
return getAverageError(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Function for logging the statistics of the motion data.
|
||||
*
|
||||
* @param referencePath The reference path to compare the motion data to.
|
||||
*/
|
||||
public void logStatistics(GesturePath referencePath) {
|
||||
Log.i("MotionProcessor", "Average path error: " + getAverageError(referencePath));
|
||||
Log.i("MotionProcessor", "Path length: " + relativePath.size());
|
||||
Log.i("MotionProcessor", "Sample rate: " + sampleRate);
|
||||
Log.i("MotionProcessor", "Calibration point: " + ZERO.toString());
|
||||
|
||||
}
|
||||
}
|
@@ -0,0 +1,258 @@
|
||||
package com.example.fitbot.util.processing;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
|
||||
public class Vector3 {
|
||||
|
||||
public double x, y, z;
|
||||
|
||||
/**
|
||||
* Constructor for creating a new vector.
|
||||
*
|
||||
* @param x The X component of the vector.
|
||||
* @param y The Y component of the vector.
|
||||
* @param z The Z component of the vector.
|
||||
*/
|
||||
public Vector3(double x, double y, double z) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.z = z;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy the vector.
|
||||
*
|
||||
* @return A new vector with the same values.
|
||||
*/
|
||||
public Vector3 copy() {
|
||||
return new Vector3(this.x, this.y, this.z);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the zero vector.
|
||||
*
|
||||
* @return The zero vector.
|
||||
*/
|
||||
public static Vector3 zero() {
|
||||
return new Vector3(0, 0, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the magnitude of the vector.
|
||||
*
|
||||
* @return The magnitude of the vector.
|
||||
*/
|
||||
public double magnitude() {
|
||||
return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z);
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize the vector.
|
||||
*
|
||||
* @return The normalized vector.
|
||||
*/
|
||||
public Vector3 normalize() {
|
||||
double mag = this.magnitude();
|
||||
if (mag == 0) throw new IllegalArgumentException("Cannot normalize the zero vector.");
|
||||
return new Vector3(this.x / mag, this.y / mag, this.z / mag);
|
||||
}
|
||||
|
||||
/**
|
||||
* Subtract the vector from another vector.
|
||||
*
|
||||
* @param other The other vector to subtract.
|
||||
* @return The new vector.
|
||||
*/
|
||||
public Vector3 subtract(Vector3 other) {
|
||||
return new Vector3(this.x - other.x, this.y - other.y, this.z - other.z);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the vector to another vector.
|
||||
*
|
||||
* @param other The other vector to add.
|
||||
* @return The new vector.
|
||||
*/
|
||||
public Vector3 add(Vector3 other) {
|
||||
return new Vector3(this.x + other.x, this.y + other.y, this.z + other.z);
|
||||
}
|
||||
|
||||
/**
|
||||
* Multiply the vector by a scalar.
|
||||
*
|
||||
* @param scalar The scalar to multiply by.
|
||||
* @return The multiplied vector.
|
||||
*/
|
||||
public Vector3 multiply(double scalar) {
|
||||
return new Vector3(this.x * scalar, this.y * scalar, this.z * scalar);
|
||||
}
|
||||
|
||||
/**
|
||||
* Divide the vector by a scalar.
|
||||
*
|
||||
* @param scalar The scalar to divide by.
|
||||
* @return The divided vector.
|
||||
*/
|
||||
public Vector3 divide(double scalar) {
|
||||
if (scalar == 0) throw new IllegalArgumentException("Cannot divide by zero.");
|
||||
return new Vector3(this.x / scalar, this.y / scalar, this.z / scalar);
|
||||
}
|
||||
|
||||
/**
|
||||
* Negate the vector.
|
||||
*
|
||||
* @return The negated vector.
|
||||
*/
|
||||
public Vector3 negate() {
|
||||
return new Vector3(-this.x, -this.y, -this.z);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rotate the vector around the X, Y, and Z axes.
|
||||
*
|
||||
* @param radX Rotation around the X axis in radians.
|
||||
* @param radY Rotation around the Y axis in radians.
|
||||
* @param radZ Rotation around the Z axis in radians.
|
||||
* @return The rotated vector.
|
||||
*/
|
||||
public Vector3 rotate(double radX, double radY, double radZ) {
|
||||
double cosX = Math.cos(radX);
|
||||
double cosY = Math.cos(radY);
|
||||
double cosZ = Math.cos(radZ);
|
||||
double sinX = Math.sin(radX);
|
||||
double sinY = Math.sin(radY);
|
||||
double sinZ = Math.sin(radZ);
|
||||
double newX = x * cosY * cosZ + y * cosY * sinZ - z * sinY;
|
||||
double newY = x * (sinX * sinY * cosZ - cosX * sinZ) + y * (sinX * sinY * sinZ + cosX * cosZ) + z * sinX * cosY;
|
||||
double newZ = x * (cosX * sinY * cosZ + sinX * sinZ) + y * (cosX * sinY * sinZ - sinX * cosZ) + z * cosX * cosY;
|
||||
return new Vector3(newX, newY, newZ);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rotate the vector around the X, Y, and Z axes.
|
||||
*
|
||||
* @param rotation The rotation vector.
|
||||
* @return The rotated vector.
|
||||
*/
|
||||
public Vector3 rotate(Vector3 rotation) {
|
||||
return rotate(rotation.x, rotation.y, rotation.z);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rotate the vector around the X axis.
|
||||
*
|
||||
* @param angle Rotation around the X axis in radians.
|
||||
* @return The rotated vector.
|
||||
*/
|
||||
public Vector3 rotateX(double angle) {
|
||||
double sinTheta = Math.sin(angle);
|
||||
double cosTheta = Math.cos(angle);
|
||||
return new Vector3(
|
||||
x,
|
||||
y * cosTheta - z * sinTheta,
|
||||
y * sinTheta + z * cosTheta
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rotate the vector around the Y axis.
|
||||
*
|
||||
* @param angle Rotation around the Y axis in radians.
|
||||
* @return The rotated vector.
|
||||
*/
|
||||
public Vector3 rotateY(double angle) {
|
||||
double sinTheta = Math.sin(angle);
|
||||
double cosTheta = Math.cos(angle);
|
||||
return new Vector3(
|
||||
x * cosTheta + z * sinTheta,
|
||||
y,
|
||||
-x * sinTheta + z * cosTheta
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rotate the vector around the Z axis.
|
||||
*
|
||||
* @param angle Rotation around the Z axis in radians.
|
||||
* @return The rotated vector.
|
||||
*/
|
||||
public Vector3 rotateZ(double angle) {
|
||||
double sinTheta = Math.sin(angle);
|
||||
double cosTheta = Math.cos(angle);
|
||||
return new Vector3(
|
||||
x * cosTheta - y * sinTheta,
|
||||
x * sinTheta + y * cosTheta,
|
||||
z
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the squared distance between this vector and another vector.
|
||||
*
|
||||
* @param compare The other vector.
|
||||
* @return The squared distance between the two vectors.
|
||||
*/
|
||||
public double distanceSq(Vector3 compare) {
|
||||
return Math.pow(compare.x - x, 2) + Math.pow(compare.y - y, 2) + Math.pow(compare.z - z, 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the distance between this vector and another vector.
|
||||
*
|
||||
* @param compare The other vector.
|
||||
* @return The distance between the two vectors.
|
||||
*/
|
||||
public double distance(Vector3 compare) {
|
||||
return Math.sqrt(distanceSq(compare));
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the distance to a line defined by two points.
|
||||
*
|
||||
* @param lineStart The starting point of the line.
|
||||
* @param lineEnd The ending point of the line.
|
||||
* @return The distance to the line.
|
||||
*/
|
||||
public double distanceToLine(Vector3 lineStart, Vector3 lineEnd) {
|
||||
double lineDistance = lineStart.distanceSq(lineEnd);
|
||||
if (lineDistance == 0)
|
||||
return this.distanceSq(lineStart);
|
||||
|
||||
double t = ((this.x - lineStart.x) * (lineEnd.x - lineStart.x) +
|
||||
(this.y - lineStart.y) * (lineEnd.y - lineStart.y) +
|
||||
(this.z - lineStart.z) * (lineEnd.z - lineStart.z)) / lineDistance;
|
||||
|
||||
t = Math.max(0, Math.min(1, t));
|
||||
|
||||
return this.distanceSq(new Vector3(
|
||||
lineStart.x + t * (lineEnd.x - lineStart.x),
|
||||
lineStart.y + t * (lineEnd.y - lineStart.y),
|
||||
lineStart.z + t * (lineEnd.z - lineStart.z))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the closest vector to this one given a list of vectors.
|
||||
*
|
||||
* @param vectors The list of vectors to compare.
|
||||
* @return The closest vector.
|
||||
*/
|
||||
public Vector3 closest(Vector3 ... vectors) {
|
||||
return Arrays.stream(vectors).min(Comparator.comparingDouble(this::distanceSq)).orElse(null);
|
||||
}
|
||||
|
||||
public Vector3 map(VectorMapFunction function) {
|
||||
return function.apply(this);
|
||||
}
|
||||
|
||||
public interface VectorMapFunction {
|
||||
Vector3 apply(Vector3 vector);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return "Vector3(" + this.x + ", " + this.y + ", " + this.z + ")";
|
||||
}
|
||||
}
|
@@ -0,0 +1,18 @@
|
||||
package com.example.fitbot.util.server;
|
||||
|
||||
import java.net.Socket;
|
||||
|
||||
/**
|
||||
* Interface for handling WebSocket events.
|
||||
*/
|
||||
public interface IWebSocketHandler {
|
||||
|
||||
// Function for handling the connection of the WebSocket.
|
||||
default void onConnected(Socket socket) {}
|
||||
|
||||
default void onDisconnected(Socket socket) {}
|
||||
|
||||
default void onMessageReceived(WebSocket.Message message, WebSocket.MessageReply replier) {}
|
||||
|
||||
default void onError(Socket socket, String error) {}
|
||||
}
|
@@ -0,0 +1,150 @@
|
||||
package com.example.fitbot.util.server;
|
||||
|
||||
import android.support.annotation.Nullable;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public class WebSocket {
|
||||
|
||||
private ServerSocket serverSocket;
|
||||
private WebSocketConnectionHandler connectionHandler;
|
||||
private final Set<Socket> clients = Collections.synchronizedSet(new HashSet<>());
|
||||
protected IWebSocketHandler eventHandler = new IWebSocketHandler() {}; // NO-OP event handler.
|
||||
|
||||
/**
|
||||
* Constructor for creating a new WebSocket server.
|
||||
*/
|
||||
private WebSocket() {}
|
||||
|
||||
/**
|
||||
* Function for creating a new WebSocket server given the provided port.
|
||||
* @return A WebSocket connection, or null if something went wrong.
|
||||
*/
|
||||
public static @Nullable WebSocket createServer() {
|
||||
try {
|
||||
WebSocket webSocket = new WebSocket();
|
||||
webSocket.serverSocket = new ServerSocket();
|
||||
webSocket.serverSocket.bind(webSocket.serverSocket.getLocalSocketAddress());
|
||||
Log.i("WebSocket -- Creating new WebSocket server", "Server created: " + webSocket.serverSocket.getLocalSocketAddress() + ", " + webSocket.serverSocket.getLocalPort());
|
||||
return webSocket;
|
||||
} catch (IOException error)
|
||||
{
|
||||
String cause = error.getMessage() == null ? "Unknown reason" : error.getMessage();
|
||||
Log.e("WebSocket -- Creating new WebSocket server", cause);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method for listening for incoming connections.
|
||||
*/
|
||||
public void startListening() {
|
||||
this.connectionHandler = new WebSocketConnectionHandler(this);
|
||||
this.connectionHandler.listen();
|
||||
}
|
||||
|
||||
/**
|
||||
* Method for stopping the WebSocket server.
|
||||
*/
|
||||
public void stop() {
|
||||
try {
|
||||
this.serverSocket.close();
|
||||
this.connectionHandler.stop();
|
||||
} catch (IOException error) {
|
||||
String cause = error.getMessage() == null ? "Unknown reason" : error.getMessage();
|
||||
Log.e("WebSocket -- Closing server connection", cause);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method for setting the event handler for this WebSocket server.
|
||||
* @param handler The handler to use. This handler will parse all events
|
||||
* that occur in this WebSocket connection. The events are the followed:
|
||||
* - onMessageReceived(Socket, String)
|
||||
* - onConnected(Socket)
|
||||
* - onDisconnected(Socket)
|
||||
* - onError(Socket, String)
|
||||
*/
|
||||
public void setEventHandler(IWebSocketHandler handler) {
|
||||
this.eventHandler = handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method for getting the ServerSocket connection
|
||||
* @return The ServerSocket connection.
|
||||
*/
|
||||
public ServerSocket getSocket() {
|
||||
return this.serverSocket;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method for checking whether this WebSocket connection is connected.
|
||||
* @return The connection status of the WebSocket.
|
||||
*/
|
||||
public boolean isConnected() {
|
||||
return !this.serverSocket.isClosed();
|
||||
}
|
||||
|
||||
/**
|
||||
* Class representing a message received from a WebSocket connection.
|
||||
*/
|
||||
public static class Message {
|
||||
|
||||
// Enumerable representing message type (opcode).
|
||||
public enum Opcode {
|
||||
|
||||
CONTINUING((byte) 0x0),
|
||||
TEXT((byte) 0x1),
|
||||
BINARY((byte) 0x2),
|
||||
RES0((byte) 0x3), RES1((byte) 0x4), RES2((byte) 0x5), RES3((byte) 0x6), RES4((byte) 0x7),
|
||||
CLOSE_CONNECTION((byte) 0x8),
|
||||
PING((byte) 0x9),
|
||||
PONG((byte) 0xA),
|
||||
RES5((byte) 0xB), RES6((byte) 0xC), RES7((byte) 0xD), RES8((byte) 0xE), RES9((byte) 0xF);
|
||||
|
||||
byte opcode;
|
||||
Opcode(final byte opcode) {
|
||||
this.opcode = opcode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method for decoding the opcode of a message.
|
||||
* @param opcode The opcode to decode.
|
||||
* @return The message type.
|
||||
*/
|
||||
public static Opcode decode(byte opcode) {
|
||||
return Opcode.values()[opcode & 0xF];
|
||||
}
|
||||
// Returns the opcode of this message type.
|
||||
public byte getOpcode() { return this.opcode; }
|
||||
}
|
||||
|
||||
public String message;
|
||||
public WebSocketConnection connection;
|
||||
|
||||
/**
|
||||
* Constructor for a WebSocket message.
|
||||
* @param message The message that was sent
|
||||
* @param connection The connection where the message came from.
|
||||
*/
|
||||
public Message(WebSocketConnection connection, String message) {
|
||||
this.message = message;
|
||||
this.connection = connection;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface for a message reply.
|
||||
* This can be used for when a message has been received from a client
|
||||
* to reply back to the client.
|
||||
*/
|
||||
public interface MessageReply {
|
||||
void reply(String message);
|
||||
}
|
||||
}
|
@@ -0,0 +1,35 @@
|
||||
package com.example.fitbot.util.server;
|
||||
|
||||
import java.net.Socket;
|
||||
|
||||
public class WebSocketConnection {
|
||||
|
||||
private final WebSocket origin;
|
||||
private final Socket socket;
|
||||
|
||||
/**
|
||||
* Constructor for creating an arbitrary WebSocket connection (Client)
|
||||
* @param connection The server connection
|
||||
* @param socket The client socket
|
||||
*/
|
||||
public WebSocketConnection(WebSocket connection, Socket socket) {
|
||||
this.origin = connection;
|
||||
this.socket = socket;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter method for retrieving the WebSocket connection
|
||||
* @return The WebSocket instance.
|
||||
*/
|
||||
public WebSocket getOrigin() {
|
||||
return origin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter method for retrieving the Client Socket connection.
|
||||
* @return The Socket connection.
|
||||
*/
|
||||
public Socket getSocket() {
|
||||
return socket;
|
||||
}
|
||||
}
|
@@ -0,0 +1,217 @@
|
||||
package com.example.fitbot.util.server;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.Socket;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Base64;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Scanner;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class WebSocketConnectionHandler implements Runnable {
|
||||
|
||||
private final WebSocket theSocket;
|
||||
private List<Socket> clients = Collections.synchronizedList(new ArrayList<>());
|
||||
private Thread thread;
|
||||
private boolean forceClose = false;
|
||||
|
||||
/**
|
||||
* Constructor for WebSocketConnectionHandler.
|
||||
* This class handles all new incoming Socket connections.
|
||||
*
|
||||
* @param webSocket The socket to check for new connections.
|
||||
*/
|
||||
protected WebSocketConnectionHandler(WebSocket webSocket) {
|
||||
this.theSocket = webSocket;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
// Listen for new connections until the socket closes.
|
||||
while (theSocket.isConnected()) {
|
||||
try {
|
||||
// Find a new connection
|
||||
Socket newSocket = this.theSocket.getSocket().accept();
|
||||
this.theSocket.eventHandler.onConnected(newSocket);
|
||||
clients.add(newSocket);
|
||||
|
||||
InputStream streamIn = newSocket.getInputStream();
|
||||
OutputStream streamOut = newSocket.getOutputStream();
|
||||
|
||||
// Check if the connection was successfully upgraded to WebSocket
|
||||
if (upgradeConnection(streamIn, streamOut)) {
|
||||
applyMessageDecoder(streamIn);
|
||||
}
|
||||
} catch (IOException error) {
|
||||
String reason = error.getMessage() == null ? "Unknown reason" : error.getMessage();
|
||||
Log.e("WebSocketConnectionHandler", "Error listening to Socket connections: " + reason);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method for upgrading a HTTP connection to a WebSocket connection.
|
||||
* This checks whether the client sent a GET header and sends back
|
||||
* the required headers to upgrade the connection.
|
||||
* @param streamIn The InputStream of the client socket connection.
|
||||
* @param streamOut The OutputStream of the client socket connection.
|
||||
* @return Whether or not the connection was successfully upgraded.
|
||||
*/
|
||||
private boolean upgradeConnection(InputStream streamIn, OutputStream streamOut) {
|
||||
Scanner scanner = new Scanner(streamIn, "UTF-8");
|
||||
String data = scanner.useDelimiter("\\r\\n\\r\\n").next();
|
||||
Matcher header = Pattern.compile("^GET").matcher(data);
|
||||
|
||||
// Check if the header contains the GET keyword
|
||||
// If this is the case, upgrade the HTTP connection to WebSocket.
|
||||
if (!header.find())
|
||||
return false;
|
||||
|
||||
Matcher match = Pattern.compile("Sec-WebSocket-Key: (.*)").matcher(data);
|
||||
match.find(); // Get next match
|
||||
|
||||
try {
|
||||
String SECAccept = Base64.getEncoder().encodeToString(
|
||||
MessageDigest.getInstance("SHA-1").digest((match.group(1) + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11").getBytes(StandardCharsets.UTF_8)));
|
||||
|
||||
byte[] response = (
|
||||
"HTTP/1.1 101 Switching Protocols\r\n" +
|
||||
"Connection: Upgrade\r\n" +
|
||||
"Upgrade: websocket\r\n" +
|
||||
"Sec-WebSocket-Accept: " +
|
||||
SECAccept + "\r\n\r\n").getBytes(StandardCharsets.UTF_8);
|
||||
streamOut.write(response, 0, response.length);
|
||||
|
||||
} catch (IOException | NoSuchAlgorithmException error) {
|
||||
Log.e("WebSocketConnectionHandler", "Failed upgrading HTTP to WebSocket connection" + error.getMessage());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method for applying a message decoder for whenever a socket receives data.
|
||||
* This method attemps to decode a message received from a WebSocket connection.
|
||||
* This message is in the
|
||||
* @param streamIn The message stream to decode.
|
||||
*/
|
||||
private void applyMessageDecoder(InputStream streamIn) {
|
||||
// TODO: Implement
|
||||
}
|
||||
|
||||
/**
|
||||
* Method for decoding an encoded WebSocket message
|
||||
* @param bytes The message to decode, in UTF-8 format.
|
||||
* @return The decoded message.
|
||||
* @throws IllegalArgumentException When the `frame` content is in an incorrect format.
|
||||
*/
|
||||
public static String decodeWebSocketMessage(byte[] bytes) {
|
||||
// Check if the packet isn't corrupted
|
||||
if (bytes.length <= 2 || (bytes[0] & 0b1110) != 0)
|
||||
throw new IllegalArgumentException("Attempted to decode corrupted WebSocket frame data");
|
||||
|
||||
WebSocket.Message.Opcode opcode = WebSocket.Message.Opcode.decode((byte) (bytes[0] & 0b11110000));
|
||||
byte payloadLength = (byte) (bytes[1] & 0b01111111); // Payload size (7 bits)
|
||||
boolean fin = (bytes[0] & 0b1) != 0; // Whether this is the whole message
|
||||
boolean masked = (bytes[1] & 0b10000000) != 0; // Whether the 9th bit is masked
|
||||
|
||||
long extendedPayloadLength = 0;
|
||||
int byteOffset = 2;
|
||||
|
||||
// Check whether the payload length is 16-bit
|
||||
if (payloadLength == 126) {
|
||||
// 16-bit extended payload length (byte 2 and 3)
|
||||
extendedPayloadLength = ((bytes[2] & 0xFF) << 8) | (bytes[3] & 0xFF);
|
||||
byteOffset += 2;
|
||||
|
||||
// Check whether payload length is 64-bit
|
||||
} else if (payloadLength == 127) {
|
||||
// 64-bit extended payload length
|
||||
for (int i = 0; i < 8; i++)
|
||||
extendedPayloadLength |= (long) (bytes[2 + i] & 0xFF) << ((7 - i) * 8);
|
||||
byteOffset += 8;
|
||||
|
||||
} else {
|
||||
extendedPayloadLength = payloadLength;
|
||||
}
|
||||
|
||||
byte[] maskingKey = null;
|
||||
byte[] payloadData = new byte[(int) extendedPayloadLength];
|
||||
|
||||
|
||||
// Check if the MASK bit was set, if so, copy the key to the `maskingKey` array.
|
||||
if (masked) {
|
||||
maskingKey = new byte[4];
|
||||
System.arraycopy(bytes, byteOffset, maskingKey, 0, 4); // move mask bytes
|
||||
byteOffset += 4;
|
||||
}
|
||||
|
||||
// Copy payload bytes into `payloadData` array.
|
||||
System.arraycopy(bytes, byteOffset, payloadData, 0, payloadData.length);
|
||||
|
||||
// If mask is present, decode the payload data with the mask.
|
||||
if (masked)
|
||||
for (int i = 0; i < payloadData.length; i++)
|
||||
payloadData[i] ^= maskingKey[i % 4];
|
||||
|
||||
// Convert payload data to string
|
||||
return new String(payloadData, StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method for checking whether the connection handler is actively listening.
|
||||
* @return Whether it's listening.
|
||||
*/
|
||||
public boolean isActive() {
|
||||
return this.thread.isAlive();
|
||||
}
|
||||
|
||||
/**
|
||||
* Method for listening to all new incoming socket connections.
|
||||
*/
|
||||
public void listen() {
|
||||
this.thread = new Thread(this);
|
||||
this.thread.start();
|
||||
Log.i("WebSocketConnectionHandler", "Listening started.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Method for stopping the connection handler.
|
||||
*/
|
||||
public void stop() {
|
||||
// Close the socket connection if not already closed
|
||||
if (!this.theSocket.getSocket().isClosed()) {
|
||||
try {
|
||||
this.theSocket.getSocket().close();
|
||||
} catch (IOException error) {
|
||||
Log.e("WebSocketConnectionHandler", "Failed to close the socket connection: " + error.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// Interrupt the thread
|
||||
this.thread.interrupt();
|
||||
|
||||
// Close all connections
|
||||
this.clients.forEach(client -> {
|
||||
try {
|
||||
client.close();
|
||||
} catch (IOException error) {
|
||||
Log.e("WebSocketConnectionHandler", "Failed to close client: " + error.getMessage());
|
||||
}
|
||||
});
|
||||
this.clients.clear();
|
||||
|
||||
|
||||
Log.i("WebSocketConnectionHandler", "Listening stopped.");
|
||||
}
|
||||
}
|
@@ -0,0 +1,36 @@
|
||||
package com.example.fitbot;
|
||||
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import com.example.fitbot.util.server.WebSocketConnectionHandler;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Created on 07/05/2024 at 18:27
|
||||
* by Luca Warmenhoven.
|
||||
*/
|
||||
public class WebSocketMessageParsingTest {
|
||||
|
||||
|
||||
|
||||
@Test
|
||||
public void parseWebSocketMessage() {
|
||||
|
||||
String reference = "abcdef";
|
||||
final byte[] encoded = {
|
||||
(byte) 129, (byte) 134, (byte) 167,
|
||||
(byte) 225, (byte) 225, (byte) 210,
|
||||
(byte) 198, (byte) 131, (byte) 130,
|
||||
(byte) 182, (byte) 194, (byte) 135
|
||||
};
|
||||
String decoded = "";
|
||||
try {
|
||||
decoded = WebSocketConnectionHandler.decodeWebSocketMessage(encoded);
|
||||
} catch (Exception e) {
|
||||
System.err.println("Error occurred whilst attempting to parse input" + e.getMessage());
|
||||
}
|
||||
assertEquals(reference, decoded);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user