Added motion processing, fixed WebSocket packet processing, added Vector math to process motion data
This commit is contained in:
29
.idea/workspace.xml
generated
29
.idea/workspace.xml
generated
@@ -15,9 +15,14 @@
|
||||
</component>
|
||||
<component name="ChangeListManager">
|
||||
<list default="true" id="00599d5b-7eb5-44da-ad7f-98bf42384c16" name="Changes" comment="Final update onderzoek-formulier.md">
|
||||
<change afterPath="$PROJECT_DIR$/docs/personalDocs/Luca/weekly-reports/sprint-1-week-1.md" afterDir="false" />
|
||||
<change afterPath="$PROJECT_DIR$/docs/personalDocs/Luca/weekly-reports/sprint-1-week-2.md" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/code/src/Fitbot/app/src/main/java/com/example/fitbot/MainActivity.java" beforeDir="false" afterPath="$PROJECT_DIR$/code/src/Fitbot/app/src/main/java/com/example/fitbot/MainActivity.java" afterDir="false" />
|
||||
<change afterPath="$PROJECT_DIR$/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/server/WebSocketConnection.java" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/processing/MotionData.java" beforeDir="false" afterPath="$PROJECT_DIR$/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/processing/MotionData.java" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/processing/MotionInputStream.java" beforeDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/processing/MotionProcessor.java" beforeDir="false" afterPath="$PROJECT_DIR$/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/processing/MotionProcessor.java" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/server/IWebSocketHandler.java" beforeDir="false" afterPath="$PROJECT_DIR$/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/server/IWebSocketHandler.java" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/server/WebSocket.java" beforeDir="false" afterPath="$PROJECT_DIR$/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/server/WebSocket.java" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/server/WebSocketConnectionHandler.java" beforeDir="false" afterPath="$PROJECT_DIR$/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/server/WebSocketConnectionHandler.java" afterDir="false" />
|
||||
</list>
|
||||
<option name="SHOW_DIALOG" value="false" />
|
||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||
@@ -70,10 +75,10 @@
|
||||
<component name="MarkdownSettingsMigration">
|
||||
<option name="stateVersion" value="1" />
|
||||
</component>
|
||||
<component name="ProjectColorInfo"><![CDATA[{
|
||||
"customColor": "",
|
||||
"associatedIndex": 7
|
||||
}]]></component>
|
||||
<component name="ProjectColorInfo">{
|
||||
"customColor": "",
|
||||
"associatedIndex": 7
|
||||
}</component>
|
||||
<component name="ProjectId" id="2fE3N2CwEPDo9wBtexBLxU20tCJ" />
|
||||
<component name="ProjectLevelVcsManager">
|
||||
<ConfirmationsSetting value="2" id="Add" />
|
||||
@@ -85,13 +90,16 @@
|
||||
<component name="PropertiesComponent"><![CDATA[{
|
||||
"keyToString": {
|
||||
"RunOnceActivity.OpenProjectViewOnStart": "true",
|
||||
"git-widget-placeholder": "Merging main",
|
||||
"RunOnceActivity.ShowReadmeOnStart": "true",
|
||||
"git-widget-placeholder": "44-als-gebruiker-wil-ik-dat-ik-feedback-krijg-als-mijn-bewegingen-niet-op…-weet",
|
||||
"kotlin-language-version-configured": "true",
|
||||
"last_opened_file_path": "/Users/lucawarm/Jetbrains/Android Studio/muupooviixee66",
|
||||
"node.js.detected.package.eslint": "true",
|
||||
"node.js.detected.package.tslint": "true",
|
||||
"node.js.selected.package.eslint": "(autodetect)",
|
||||
"node.js.selected.package.tslint": "(autodetect)",
|
||||
"nodejs_package_manager_path": "npm",
|
||||
"settings.editor.selected.configurable": "preferences.pluginManager",
|
||||
"vue.rearranger.settings.migration": "true"
|
||||
}
|
||||
}]]></component>
|
||||
@@ -118,10 +126,7 @@
|
||||
<option name="INSPECTION_WITHOUT_ACTIVITY_RESTART" value="false" />
|
||||
<option name="TARGET_SELECTION_MODE" value="SHOW_DIALOG" />
|
||||
<option name="DEBUGGER_TYPE" value="Java" />
|
||||
<Java>
|
||||
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
|
||||
<option name="DEBUG_SANDBOX_SDK" value="false" />
|
||||
</Java>
|
||||
<Java />
|
||||
<Profilers>
|
||||
<option name="ADVANCED_PROFILING_ENABLED" value="false" />
|
||||
<option name="STARTUP_PROFILING_ENABLED" value="false" />
|
||||
|
@@ -1,14 +1,11 @@
|
||||
package com.example.fitbot.util.processing;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
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;
|
||||
public Vector3 acceleration, rotation;
|
||||
|
||||
// Delimiter for the data received from the motion sensor
|
||||
private static final String DATA_DELIMITER = ";";
|
||||
@@ -24,12 +21,19 @@ public class MotionData {
|
||||
* @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;
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -41,7 +45,12 @@ public class MotionData {
|
||||
* @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(
|
||||
Float.parseFloat(parts[0]),
|
||||
Float.parseFloat(parts[1]),
|
||||
|
@@ -1,38 +0,0 @@
|
||||
package com.example.fitbot.util.processing;
|
||||
|
||||
import com.example.fitbot.util.server.WebSocket;
|
||||
|
||||
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 socket server
|
||||
WebSocket socket = WebSocket.createServer(80);
|
||||
|
||||
// Check if the socket
|
||||
if ( socket != null )
|
||||
{
|
||||
socket.startListening();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int read() {
|
||||
return 0;
|
||||
}
|
||||
}
|
@@ -1,4 +1,95 @@
|
||||
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;
|
||||
|
||||
public class MotionProcessor {
|
||||
|
||||
private static final int MAX_PATH_LENGTH = 100;
|
||||
public static final String DELIMITER = ";";
|
||||
|
||||
private final List<MotionData> motionData = new ArrayList<>();
|
||||
private final Vector3[] relativePath = new Vector3[MAX_PATH_LENGTH];
|
||||
private Vector3 ZERO = new Vector3(0, 0, 0);
|
||||
private int sampleRate = 1;
|
||||
|
||||
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
|
||||
WebSocket socket = WebSocket.createServer(80);
|
||||
|
||||
// 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 parsing arbitrary packet data.
|
||||
* @param data The data to parse.
|
||||
*/
|
||||
private void parsePacket(@NotNull String data) {
|
||||
// If the message starts with 'data', it's a data packet.
|
||||
if ( data.startsWith("data")) {
|
||||
MotionData parsedData = MotionData.decode(data.split(" ")[1]);
|
||||
if (parsedData != null) {
|
||||
this.motionData.add(parsedData);
|
||||
}
|
||||
// Otherwise check if it starts with 'calibrate', this is the ZERO point.
|
||||
} else if ( data.startsWith("calibrate")) { // 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 = Integer.parseInt(data.split(" ")[1]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Function for calculating the path of the device.
|
||||
* This only works when the ZERO point has been provided.
|
||||
*/
|
||||
private void calculatePath() {
|
||||
|
||||
int offset = Math.max(0, motionData.size() - MAX_PATH_LENGTH);
|
||||
Vector3 previous = ZERO.copy();
|
||||
|
||||
for (int i = offset; i < motionData.size(); i++) {
|
||||
MotionData data = motionData.get(i);
|
||||
|
||||
// Get relative vector from the ZERO point
|
||||
Vector3 relativePosition = data.acceleration
|
||||
.rotate(data.rotation.negate()) // Rotate acceleration vector with it's angle to get the acceleration relative to the vector of gravity
|
||||
.map((vector) -> vector.) // Convert acceleration to relative spatial change
|
||||
|
||||
relativePath[i - offset]
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -0,0 +1,165 @@
|
||||
package com.example.fitbot.util.processing;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
);
|
||||
}
|
||||
|
||||
public Vector3 map(VectorMapFunction function) {
|
||||
return function.apply(this);
|
||||
}
|
||||
|
||||
public interface VectorMapFunction {
|
||||
Vector3 apply(Vector3 vector);
|
||||
}
|
||||
}
|
@@ -9,7 +9,10 @@ public interface IWebSocketHandler {
|
||||
|
||||
// Function for handling the connection of the WebSocket.
|
||||
default void onConnected(Socket socket) {}
|
||||
|
||||
default void onDisconnected(Socket socket) {}
|
||||
default void onMessageReceived(String message) {}
|
||||
|
||||
default void onMessageReceived(WebSocket.Message message, WebSocket.MessageReply replier) {}
|
||||
|
||||
default void onError(Socket socket, String error) {}
|
||||
}
|
||||
|
@@ -48,6 +48,19 @@ public class WebSocket {
|
||||
this.connectionHandler.listen();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
@@ -63,4 +76,61 @@ public class 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 Type {
|
||||
|
||||
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;
|
||||
Type(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 Type decode(byte opcode) {
|
||||
return Type.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;
|
||||
}
|
||||
}
|
@@ -5,6 +5,8 @@ import android.support.annotation.RequiresApi;
|
||||
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;
|
||||
@@ -22,6 +24,7 @@ public class WebSocketConnectionHandler implements Runnable {
|
||||
/**
|
||||
* Constructor for WebSocketConnectionHandler.
|
||||
* This class handles all new incoming Socket connections.
|
||||
*
|
||||
* @param webSocket The socket to check for new connections.
|
||||
*/
|
||||
protected WebSocketConnectionHandler(WebSocket webSocket) {
|
||||
@@ -31,37 +34,22 @@ public class WebSocketConnectionHandler implements Runnable {
|
||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||
@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);
|
||||
|
||||
Scanner scanner = new Scanner(newSocket.getInputStream(), "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()) {
|
||||
Matcher match = Pattern.compile("Sec-WebSocket-Key: (.*)").matcher(data);
|
||||
match.find();
|
||||
|
||||
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);
|
||||
newSocket.getOutputStream().write(response, 0, response.length);
|
||||
|
||||
// TODO: Read data from socket and provide in onMessageReceived.
|
||||
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 | NoSuchAlgorithmException error) {
|
||||
} catch (IOException error) {
|
||||
String reason = error.getMessage() == null ? "Unknown reason" : error.getMessage();
|
||||
Log.e("WebSocketConnectionHandler", "Error listening to Socket connections: " + reason);
|
||||
break;
|
||||
@@ -69,9 +57,121 @@ public class WebSocketConnectionHandler implements Runnable {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||
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) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 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");
|
||||
|
||||
byte opcode = (byte) (bytes[0] & 0b11110000); // Opcode (4 bits)
|
||||
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
|
||||
|
||||
System.out.printf("Fin: %b, Opcode: %d, masked: %b\n", fin, opcode, 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 or not it's listening.
|
||||
* @return Whether it's listening.
|
||||
*/
|
||||
public boolean isActive() {
|
||||
return this.thread.isAlive();
|
||||
|
@@ -0,0 +1,37 @@
|
||||
package com.example.fitbot;
|
||||
|
||||
|
||||
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());
|
||||
}
|
||||
System.out.println("Parsed output: '" + decoded + "'");
|
||||
if (decoded.equals(reference)) {
|
||||
System.out.println("Test passed");
|
||||
} else {
|
||||
System.out.println("Test failed");
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,3 +1,25 @@
|
||||
## Hoe kan technologie worden ingezet om cognitieve achteruitgang tegen te gaan?
|
||||
|
||||
|
||||
### Welke oorzaken zijn er voor cognitieve achteruitgang?
|
||||
|
||||
### Wat voor methodes zijn er om cognitieve achteruitgang te remmen?
|
||||
|
||||
### Hoe kan techniek worden toegepast om
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Inleiding: ~10%
|
||||
Conclusie: ~10%
|
||||
~300 woorden totaal
|
||||
|
||||
Minimaal 5 bronnen zoeken
|
||||
|
||||
|
||||
|
||||
## Hoe kunnen ethische richtlijnen worden ontwikkeld voor het toepassen van kunstmatige intelligentie bij het aanpakken van eenzaamheid onder ouderen?
|
||||
|
||||
---
|
||||
|
Reference in New Issue
Block a user