Gitter Committer Crack balls
This commit is contained in:
@@ -3,12 +3,13 @@ package com.example.fitbot.exercise;
|
||||
import android.util.Log;
|
||||
|
||||
import com.example.fitbot.util.path.GesturePath;
|
||||
import com.example.fitbot.util.server.IWebSocketHandler;
|
||||
import com.example.fitbot.util.server.WebSocket;
|
||||
import com.example.fitbot.util.server.IWebServerHandler;
|
||||
import com.example.fitbot.util.server.WebServer;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class Exercise implements IWebSocketHandler {
|
||||
public class Exercise implements Consumer<String> {
|
||||
|
||||
private EMuscleGroup muscleGroup;
|
||||
private GesturePath path;
|
||||
@@ -17,7 +18,7 @@ public class Exercise implements IWebSocketHandler {
|
||||
private float segmentsPerSecond;
|
||||
|
||||
// Static fields.
|
||||
private static WebSocket webSocket;
|
||||
private static WebServer webSocket;
|
||||
private static Exercise currentExercise = null;
|
||||
|
||||
|
||||
@@ -57,10 +58,9 @@ public class Exercise implements IWebSocketHandler {
|
||||
}
|
||||
|
||||
try {
|
||||
webSocket = WebSocket.createServer();
|
||||
webSocket = WebServer.createServer();
|
||||
Objects.requireNonNull(webSocket, "WebSocket server could not be created.");
|
||||
|
||||
webSocket.startListening();
|
||||
webSocket.setEventHandler(this);
|
||||
currentExercise = this;
|
||||
} catch (Exception e) {
|
||||
@@ -126,4 +126,9 @@ public class Exercise implements IWebSocketHandler {
|
||||
public double getSegmentsPerSecond() {
|
||||
return segmentsPerSecond;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void accept(String message) {
|
||||
|
||||
}
|
||||
}
|
||||
|
@@ -1,41 +0,0 @@
|
||||
package com.example.fitbot.exercise;
|
||||
|
||||
import com.example.fitbot.util.processing.IMotionDataConsumer;
|
||||
import com.example.fitbot.util.processing.MotionData;
|
||||
import com.example.fitbot.util.processing.MotionProcessor;
|
||||
import com.example.fitbot.util.server.IWebSocketHandler;
|
||||
import com.example.fitbot.util.server.WebSocket;
|
||||
|
||||
import org.joml.Vector3f;
|
||||
|
||||
import java.net.Socket;
|
||||
|
||||
public class ExerciseBuilder implements IWebSocketHandler, IMotionDataConsumer {
|
||||
|
||||
private MotionProcessor processor;
|
||||
|
||||
public ExerciseBuilder() {
|
||||
this.processor = new MotionProcessor();
|
||||
this.processor.setMotionDataEventHandler(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisconnected(Socket socket) {
|
||||
IWebSocketHandler.super.onDisconnected(socket);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMessageReceived(WebSocket.Message message, WebSocket.MessageReply replier) {
|
||||
IWebSocketHandler.super.onMessageReceived(message, replier);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Socket socket, String error) {
|
||||
IWebSocketHandler.super.onError(socket, error);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void accept(Vector3f transformedVector, MotionData motionData, int sampleIndex, double sampleRate, int sensorId) {
|
||||
|
||||
}
|
||||
}
|
@@ -1,43 +0,0 @@
|
||||
package com.example.fitbot.speech;
|
||||
|
||||
import com.aldebaran.qi.sdk.QiContext;
|
||||
import com.aldebaran.qi.sdk.builder.SayBuilder;
|
||||
import com.aldebaran.qi.sdk.object.locale.Language;
|
||||
import com.aldebaran.qi.sdk.object.locale.Locale;
|
||||
import com.aldebaran.qi.sdk.object.locale.Region;
|
||||
|
||||
/**
|
||||
* SpeechGenerator class for generating speech for the robot
|
||||
*/
|
||||
public class SpeechGenerator {
|
||||
|
||||
private static final Locale DUTCH_LOCALE = new Locale(Language.DUTCH, Region.NETHERLANDS);
|
||||
private SayBuilder builder;
|
||||
|
||||
/**
|
||||
* Function for making the robot say something with DUTCH_LOCALE as locale
|
||||
* @param phrase The phrase to make the robot say
|
||||
* @param ctx The QiContext to use
|
||||
*/
|
||||
public static void say(String phrase, QiContext ctx)
|
||||
{
|
||||
say(phrase, ctx, DUTCH_LOCALE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Function for making the robot say something with a specific locale
|
||||
* @param phrase The phrase to make the robot say
|
||||
* @param ctx The QiContext to use
|
||||
* @param locale The locale to use
|
||||
*/
|
||||
public static void say(String phrase, QiContext ctx, Locale locale)
|
||||
{
|
||||
SayBuilder
|
||||
.with(ctx)
|
||||
.withLocale(locale)
|
||||
.withText(phrase)
|
||||
.build()
|
||||
.run();
|
||||
}
|
||||
|
||||
}
|
@@ -1,7 +1,6 @@
|
||||
package com.example.fitbot.ui.activities;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.util.Log;
|
||||
import android.widget.VideoView;
|
||||
|
||||
@@ -11,16 +10,17 @@ import com.aldebaran.qi.sdk.RobotLifecycleCallbacks;
|
||||
import com.aldebaran.qi.sdk.design.activity.RobotActivity;
|
||||
import com.aldebaran.qi.sdk.design.activity.conversationstatus.SpeechBarDisplayStrategy;
|
||||
import com.example.fitbot.R;
|
||||
import com.example.fitbot.sports.FitnessCycle;
|
||||
import com.example.fitbot.exercise.EMuscleGroup;
|
||||
import com.example.fitbot.exercise.Exercise;
|
||||
import com.example.fitbot.ui.components.PersonalMotionPreviewElement;
|
||||
import com.example.fitbot.util.Animations;
|
||||
import com.example.fitbot.util.ButtonNavigation;
|
||||
import com.example.fitbot.util.FitnessCycle;
|
||||
import com.example.fitbot.util.path.GesturePath;
|
||||
|
||||
import org.joml.Vector3f;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public class FitnessActivity extends RobotActivity implements RobotLifecycleCallbacks {
|
||||
|
||||
PersonalMotionPreviewElement personalMotionPreviewElement;
|
||||
@@ -71,11 +71,9 @@ public class FitnessActivity extends RobotActivity implements RobotLifecycleCall
|
||||
public void onRobotFocusGained(QiContext qiContext) {
|
||||
|
||||
// Find the VideoView by its ID
|
||||
FitnessCycle.RobotMovement("bicepcurl", 10, qiContext);
|
||||
|
||||
// FitnessCycle.playVideo(qiContext, videoView, this);
|
||||
|
||||
CompletableFuture.runAsync(() -> FitnessCycle.executeMovement("bicepcurl", 10, qiContext));
|
||||
personalMotionPreviewElement.provideQiContext(qiContext);
|
||||
// FitnessCycle.playVideo(qiContext, videoView, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -88,14 +86,8 @@ public class FitnessActivity extends RobotActivity implements RobotLifecycleCall
|
||||
// Implement your logic when the robot focus is refused
|
||||
}
|
||||
|
||||
private Handler handler;
|
||||
private Runnable runnable;
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
if (handler != null && runnable != null) {
|
||||
handler.removeCallbacks(runnable);
|
||||
}
|
||||
}
|
||||
}
|
@@ -2,6 +2,7 @@ package com.example.fitbot.ui.components;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Path;
|
||||
import android.util.AttributeSet;
|
||||
@@ -10,7 +11,7 @@ import android.view.View;
|
||||
|
||||
import com.aldebaran.qi.sdk.QiContext;
|
||||
import com.example.fitbot.exercise.Exercise;
|
||||
import com.example.fitbot.speech.SpeechGenerator;
|
||||
import com.example.fitbot.util.FitnessCycle;
|
||||
import com.example.fitbot.util.path.GesturePath;
|
||||
import com.example.fitbot.util.path.PathSegment;
|
||||
import com.example.fitbot.util.processing.MotionData;
|
||||
@@ -24,20 +25,22 @@ import org.joml.Vector4f;
|
||||
public class PersonalMotionPreviewElement extends View {
|
||||
|
||||
private GesturePath path;
|
||||
private double pathTime = 0.0D; // The timestamp at which the path is currently at.
|
||||
private MotionProcessor motionProcessor;
|
||||
|
||||
private double pathTime = 0.0D; // The timestamp at which the path is currently at.
|
||||
private double exerciseProgress = 0.0D; // The progress of the exercise. Ranges from 0 to 1.
|
||||
|
||||
private QiContext qiContext;
|
||||
|
||||
private Exercise exercise;
|
||||
|
||||
private Path referencePath; // The path the user is supposed to follow.
|
||||
private Path performingPath; // The path the user is currently following.
|
||||
private Path stickmanPath; // The path of the stickman that is drawn on the screen.
|
||||
private Path targetPath; // The path the user is supposed to follow.
|
||||
private Path actualPath; // The path the user is currently following.
|
||||
|
||||
private final Paint referencePaint = new Paint();
|
||||
private final Paint targetPaint = new Paint();
|
||||
private final Paint backgroundColor = new Paint();
|
||||
|
||||
private Paint referencePaint;
|
||||
private Paint performingPaint;
|
||||
private Paint textPaint;
|
||||
|
||||
private static final String[] USER_PHRASES = {
|
||||
"Veel success met de oefening!",
|
||||
@@ -45,76 +48,24 @@ public class PersonalMotionPreviewElement extends View {
|
||||
"Veel plezier!"
|
||||
};
|
||||
|
||||
// Matrices for the projection of the path segments onto the screen.
|
||||
// Depth buffering sadly is not supported yet due to brain dysfunction
|
||||
private Matrix4f modelViewMatrix = new Matrix4f();
|
||||
private Matrix4f projectionMatrix = new Matrix4f();
|
||||
|
||||
private double timePassed = 0.0D; // The time that has passed since the start of the exercise, in seconds.
|
||||
private long startingTime = 0L;
|
||||
|
||||
private Paint backgroundColor = new Paint();
|
||||
|
||||
/**
|
||||
* Constants for the preview path projection.
|
||||
*/
|
||||
private final float FOV = 80.0f; // The field of view of the preview path
|
||||
private final float Z_NEAR = 0.1f; // The near clipping plane
|
||||
private final float Z_FAR = 1000.0f; // The far clipping plane
|
||||
private Vector3f objectPosition = new Vector3f(0.0f, 0.0f, -4.0f); // The position of the camera
|
||||
private Vector2f screenDimensions = new Vector2f(); // Width and height dimensions of the screen
|
||||
private Vector2f rotation = new Vector2f(); // Rotation vector (yaw, pitch)
|
||||
|
||||
public PersonalMotionPreviewElement(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
|
||||
this.referencePaint = new Paint();
|
||||
this.referencePaint.setColor(0xFFFF0000); // Red
|
||||
this.referencePaint.setStyle(Paint.Style.STROKE);
|
||||
this.referencePaint.setStyle(Paint.Style.FILL);
|
||||
this.referencePaint.setStrokeWidth(5.0f);
|
||||
this.referencePaint.setAntiAlias(true);
|
||||
|
||||
this.performingPaint = new Paint();
|
||||
this.performingPaint.setColor(0xFF0000FF); // Blue
|
||||
this.performingPaint.setStyle(Paint.Style.STROKE);
|
||||
this.performingPaint.setStrokeWidth(5.0f);
|
||||
|
||||
this.textPaint = new Paint();
|
||||
this.textPaint.setColor(-1);
|
||||
this.textPaint.setStyle(Paint.Style.FILL);
|
||||
this.textPaint.setTextSize(50.0f);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method for updating the stickman gestures.
|
||||
*
|
||||
* This method will update the stickman gestures based on the current
|
||||
* motion data that is being processed.
|
||||
*/
|
||||
private void updateStickmanGestures() {
|
||||
// Reset previous path
|
||||
stickmanPath.reset();
|
||||
|
||||
// TODO: Define all arm segments:
|
||||
// - Upper left and right arm
|
||||
// - Lower left and right arm
|
||||
// - Upper left and right leg
|
||||
// - Lower left and right leg
|
||||
// Update all segments based on the perceived motion data.
|
||||
PathSegment upperLeftArm = new PathSegment(
|
||||
new Vector3f(),
|
||||
new Vector3f()
|
||||
);
|
||||
|
||||
PathSegment[] bodySegments = new PathSegment[] {
|
||||
new PathSegment(new Vector3f(0.0f, -.5f, -.5f), new Vector3f(0, 0, 0)), // Left leg
|
||||
new PathSegment(new Vector3f(0.0f, -.5f, .5f), new Vector3f(0, 0, 0)), // Right leg
|
||||
new PathSegment(new Vector3f(0.0f, .5f, 0.0f), new Vector3f(0, 0, 0)), // Body
|
||||
new PathSegment(new Vector3f(-.25f, .25f, 0f), new Vector3f(0, 0, 0)), // Left arm
|
||||
new PathSegment(new Vector3f(.25f, .25f, 0f), new Vector3f(0, 0, 0)) // Right arm
|
||||
};
|
||||
|
||||
// TODO: Generate new path for stickman
|
||||
|
||||
// Target paint is the filling of the target path.
|
||||
this.targetPaint.setColor(-1);
|
||||
this.targetPaint.setStyle(Paint.Style.STROKE);
|
||||
this.targetPaint.setStrokeWidth(5.0f);
|
||||
this.targetPaint.setAntiAlias(true);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -127,13 +78,12 @@ public class PersonalMotionPreviewElement extends View {
|
||||
*/
|
||||
public void initialize(Exercise exercise) {
|
||||
Log.i("PersonalMotionPreviewElement", "Creating new PersonalMotionPreviewElement.");
|
||||
this.backgroundColor = new Paint();
|
||||
this.backgroundColor.setColor(0xFF000000); // Black
|
||||
|
||||
this.screenDimensions.x = this.getWidth();
|
||||
this.screenDimensions.y = this.getHeight();
|
||||
this.performingPath = new Path();
|
||||
this.referencePath = new Path();
|
||||
this.actualPath = new Path();
|
||||
this.targetPath = new Path();
|
||||
|
||||
this.startingTime = System.nanoTime(); // Set the last time to the current time
|
||||
|
||||
@@ -148,6 +98,9 @@ public class PersonalMotionPreviewElement extends View {
|
||||
|
||||
/**
|
||||
* Function for providing a QiContext to the PersonalMotionPreviewElement.
|
||||
* This function will be called by the parent activity when the QiContext is available.
|
||||
* Also say something nice to the user :)
|
||||
*
|
||||
* @param context The QiContext to provide.
|
||||
*/
|
||||
public void provideQiContext(QiContext context) {
|
||||
@@ -163,90 +116,16 @@ public class PersonalMotionPreviewElement extends View {
|
||||
if (this.qiContext == null)
|
||||
return;
|
||||
|
||||
SpeechGenerator.say(USER_PHRASES[(int) Math.floor(Math.random() * USER_PHRASES.length)], this.qiContext);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method that calculates the path that will be drawn on the
|
||||
* canvas. This method will be called every time new motion data is received.
|
||||
*/
|
||||
private void calculateDrawingPath(Vector3f transformedVector, MotionData motionData, int sampleIndex, double sampleRate) {
|
||||
// Recalculate the personal path based on the new motion data
|
||||
// TODO: Implement
|
||||
FitnessCycle.say(USER_PHRASES[(int) Math.floor(Math.random() * USER_PHRASES.length)], this.qiContext);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method for setting the gesture path that will be drawn on the canvas.
|
||||
*
|
||||
* @param path The gesture path to draw.
|
||||
* @param exercise The exercise that the user is currently performing.
|
||||
*/
|
||||
public void setGesturePath(GesturePath path) {
|
||||
this.path = path;
|
||||
this.referencePath = getDrawablePath(path.getSegments());
|
||||
}
|
||||
|
||||
/**
|
||||
* Method for projecting a 3D point onto the screen.
|
||||
* This method converts the 3D point to 2D space using a Model-View-Projection matrix transformation.
|
||||
*
|
||||
* @param point The point to cast to the screen.
|
||||
* @param virtualWidth The width of the virtual screen.
|
||||
* This is used to normalize the screen coordinates.
|
||||
* @param virtualHeight The height of the virtual screen.
|
||||
* @return The transformed vector in screen coordinates ranging from (0, 0) to (virtualWidth, virtualHeight).
|
||||
*/
|
||||
private Vector2f projectVertex(Vector3f point, int virtualWidth, int virtualHeight) {
|
||||
|
||||
modelViewMatrix
|
||||
.identity()
|
||||
.translate(-objectPosition.x, -objectPosition.y, -objectPosition.z)
|
||||
.rotateX((float) Math.toRadians(rotation.y))
|
||||
.rotateY((float) Math.toRadians(rotation.x));
|
||||
|
||||
// Transform the projection matrix to a perspective projection matrix
|
||||
// Perspective transformation conserves the depth of the object
|
||||
projectionMatrix
|
||||
.identity()
|
||||
.perspective((float) Math.toRadians(FOV), (float) virtualWidth / virtualHeight, Z_NEAR, Z_FAR);
|
||||
|
||||
// Convert world coordinates to screen-space using MVP matrix
|
||||
Vector4f screenCoordinates = new Vector4f(point, 1.0f)
|
||||
.mul(this.modelViewMatrix)
|
||||
.mul(this.projectionMatrix);
|
||||
|
||||
// Normalize screen coordinates from (-1, 1) to (0, virtualWidth) and (0, virtualHeight)
|
||||
float normalizedX = (screenCoordinates.x / screenCoordinates.w + 1.0f) * 0.5f * virtualWidth;
|
||||
float normalizedY = (1.0f - screenCoordinates.y / screenCoordinates.w) * 0.5f * virtualHeight;
|
||||
Log.i("VertexProjection", "Projected vertex to screen coordinates: (" + normalizedX + ", " + normalizedY + ").");
|
||||
return new Vector2f(normalizedX, normalizedY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method that converts a sequence of vectors to a Path object.
|
||||
* This path is a set of bezier curves that will be drawn on the canvas.
|
||||
*
|
||||
* @param segments The path segments in the path.
|
||||
* These segments will be connected by bezier curves, which
|
||||
* all have unique curvature values.
|
||||
* @return The generated path object.
|
||||
*/
|
||||
private Path getDrawablePath(PathSegment... segments) {
|
||||
|
||||
Path calculatedPath = new Path();
|
||||
|
||||
// Starting point
|
||||
Vector2f origin = projectVertex(segments[0].getStart(), getWidth(), getHeight());
|
||||
calculatedPath.moveTo(origin.x, origin.y);
|
||||
|
||||
// Draw the path segments
|
||||
for (PathSegment segment : segments) {
|
||||
Vector2f startProjected = projectVertex(segment.getStart(), getWidth(), getHeight());
|
||||
Vector2f endProjected = projectVertex(segment.getEnd(), getWidth(), getHeight());
|
||||
calculatedPath.lineTo(startProjected.x, startProjected.y);
|
||||
calculatedPath.lineTo(endProjected.x, endProjected.y);
|
||||
}
|
||||
|
||||
return calculatedPath;
|
||||
public void setExercise(Exercise exercise) {
|
||||
this.exercise = exercise;
|
||||
}
|
||||
|
||||
|
||||
@@ -256,14 +135,23 @@ public class PersonalMotionPreviewElement extends View {
|
||||
this.setBackgroundColor(0xFF000000); // Black
|
||||
if (this.exercise == null)
|
||||
return;
|
||||
// Draw the sport preview canvas
|
||||
canvas.drawPath(referencePath, referencePaint);
|
||||
canvas.drawPath(performingPath, performingPaint);
|
||||
|
||||
// Draw target circle
|
||||
float targetRadius = (this.screenDimensions.x + this.screenDimensions.y) / 5.0f;
|
||||
canvas.drawCircle(this.screenDimensions.x / 2, this.screenDimensions.y / 2, targetRadius, this.targetPaint);
|
||||
canvas.drawCircle(this.screenDimensions.x / 2, this.screenDimensions.y / 2, (float)(targetRadius * exerciseProgress), this.referencePaint);
|
||||
referencePaint.setColor(
|
||||
Color.argb(
|
||||
255,
|
||||
(int)(255 * (1.0 - exerciseProgress)),
|
||||
(int)(255 * exerciseProgress),
|
||||
0
|
||||
)
|
||||
);
|
||||
|
||||
timePassed = (System.nanoTime() - startingTime) / 1E9D;
|
||||
this.exerciseProgress = timePassed*.4 % 1.0d;
|
||||
|
||||
this.rotation.x = (float) (Math.sin(timePassed) * 45);
|
||||
this.referencePath = getDrawablePath(this.path.getSegments());
|
||||
this.invalidate(); // Causes a redraw.
|
||||
}
|
||||
}
|
||||
|
@@ -1,13 +1,15 @@
|
||||
package com.example.fitbot.sports;
|
||||
|
||||
import static com.example.fitbot.sports.Animations.PlayAnimation;
|
||||
package com.example.fitbot.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.os.Handler;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.util.Log;
|
||||
import android.widget.VideoView;
|
||||
|
||||
import com.aldebaran.qi.sdk.builder.SayBuilder;
|
||||
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.R;
|
||||
import com.aldebaran.qi.sdk.QiContext;
|
||||
import com.aldebaran.qi.sdk.builder.AnimateBuilder;
|
||||
@@ -19,7 +21,18 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
public class FitnessCycle extends AppCompatActivity {
|
||||
|
||||
public static void RobotMovement(String Exercise, int Reps, QiContext qiContext) {
|
||||
private static final Locale DUTCH_LOCALE = new Locale(Language.DUTCH, Region.NETHERLANDS);
|
||||
|
||||
|
||||
/**
|
||||
* Function for executing a movement animation a certain number of times
|
||||
* on the robot
|
||||
*
|
||||
* @param Exercise The name of the exercise to perform
|
||||
* @param Reps The number of repetitions to perform
|
||||
* @param qiContext The QiContext to use
|
||||
*/
|
||||
public static void executeMovement(String Exercise, int Reps, QiContext qiContext) {
|
||||
AtomicInteger repCount = new AtomicInteger(0);
|
||||
|
||||
Animation animation = AnimationBuilder.with(qiContext)
|
||||
@@ -44,6 +57,38 @@ public class FitnessCycle extends AppCompatActivity {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Function for making the robot say something with DUTCH_LOCALE as locale
|
||||
* @param phrase The phrase to make the robot say
|
||||
* @param ctx The QiContext to use
|
||||
*/
|
||||
public static void say(String phrase, QiContext ctx)
|
||||
{
|
||||
say(phrase, ctx, DUTCH_LOCALE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Function for making the robot say something with a specific locale
|
||||
* @param phrase The phrase to make the robot say
|
||||
* @param ctx The QiContext to use
|
||||
* @param locale The locale to use
|
||||
*/
|
||||
public static void say(String phrase, QiContext ctx, Locale locale)
|
||||
{
|
||||
SayBuilder
|
||||
.with(ctx)
|
||||
.withLocale(locale)
|
||||
.withText(phrase)
|
||||
.build()
|
||||
.run();
|
||||
}
|
||||
|
||||
/**
|
||||
* Function for playing a video in a VideoView
|
||||
*
|
||||
* @param videoView The VideoView to play the video in
|
||||
* @param context The context to use
|
||||
*/
|
||||
public static void playVideo(VideoView videoView, Context context) {
|
||||
// Set up the video player
|
||||
if (videoView != null) {
|
||||
|
@@ -3,8 +3,8 @@ package com.example.fitbot.util.processing;
|
||||
import android.util.Log;
|
||||
|
||||
import com.example.fitbot.util.path.GesturePath;
|
||||
import com.example.fitbot.util.server.IWebSocketHandler;
|
||||
import com.example.fitbot.util.server.WebSocket;
|
||||
import com.example.fitbot.util.server.IWebServerHandler;
|
||||
import com.example.fitbot.util.server.WebServer;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.joml.Vector3f;
|
||||
@@ -24,7 +24,7 @@ public class MotionProcessor {
|
||||
private float sampleRate = 1.0F; // samples/second
|
||||
private IMotionDataConsumer motionDataConsumer = (p1, p2, p3, p4, p5) -> {};
|
||||
private GesturePath path;
|
||||
private WebSocket socket;
|
||||
private WebServer server;
|
||||
|
||||
public MotionProcessor() {}
|
||||
|
||||
@@ -37,20 +37,14 @@ public class MotionProcessor {
|
||||
*/
|
||||
public void startListening() {
|
||||
// Create socket server
|
||||
this.socket = WebSocket.createServer();
|
||||
this.server = WebServer.createServer();
|
||||
|
||||
Log.i("MotionProcessor", "Listening for incoming connections.");
|
||||
|
||||
// Check if the socket
|
||||
if (socket != null) {
|
||||
if (server != 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();
|
||||
server.setEventHandler(this::parsePacket);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,8 +54,8 @@ public class MotionProcessor {
|
||||
* the WebSocket server.
|
||||
*/
|
||||
public void stopListening() {
|
||||
if (socket != null) {
|
||||
socket.stop();
|
||||
if (server != null) {
|
||||
server.stop();
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -0,0 +1,11 @@
|
||||
package com.example.fitbot.util.server;
|
||||
|
||||
import java.net.Socket;
|
||||
|
||||
/**
|
||||
* Interface for handling WebSocket events.
|
||||
*/
|
||||
public interface IWebServerHandler {
|
||||
|
||||
void onReceive(String body);
|
||||
}
|
@@ -1,18 +0,0 @@
|
||||
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,129 @@
|
||||
package com.example.fitbot.util.server;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class WebServer implements Runnable {
|
||||
|
||||
private ServerSocket serverSocket;
|
||||
protected Consumer<String> eventHandler = (input) -> {}; // No-op.
|
||||
|
||||
private Thread thread;
|
||||
private AtomicBoolean forceClose = new AtomicBoolean(false);
|
||||
|
||||
/**
|
||||
* Constructor for creating a new WebSocket server.
|
||||
*/
|
||||
private WebServer() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Function for creating a new WebSocket server given the provided port.
|
||||
*
|
||||
* @return A WebSocket connection, or null if something went wrong.
|
||||
*/
|
||||
public static WebServer createServer() {
|
||||
try {
|
||||
WebServer server = new WebServer();
|
||||
server.serverSocket = new ServerSocket();
|
||||
server.serverSocket.bind(server.serverSocket.getLocalSocketAddress());
|
||||
server.serverSocket.setSoTimeout(0);
|
||||
|
||||
Log.i("WebServer -- Creating new Web Server", "Server created: " + server.serverSocket.getLocalSocketAddress() + ", " + server.serverSocket.getLocalPort());
|
||||
|
||||
server.thread = new Thread(server);
|
||||
server.thread.start();
|
||||
|
||||
return server;
|
||||
} catch (IOException error) {
|
||||
String cause = error.getMessage() == null ? "Unknown reason" : error.getMessage();
|
||||
Log.e("WebServer -- Creating new Web Server", cause);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
// Listen for new connections until the socket closes.
|
||||
while (this.isConnected() && !this.forceClose.get()) {
|
||||
try {
|
||||
// Find a new connection
|
||||
Socket newSocket = this.serverSocket.accept();
|
||||
InputStream streamIn = newSocket.getInputStream();
|
||||
|
||||
// Read the incoming data
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(streamIn));
|
||||
StringBuilder builder = new StringBuilder();
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null)
|
||||
builder.append(line);
|
||||
|
||||
newSocket.close();
|
||||
|
||||
this.eventHandler.accept(builder.toString());
|
||||
|
||||
} catch (IOException error) {
|
||||
String reason = error.getMessage() == null ? "Unknown reason" : error.getMessage();
|
||||
Log.e("WebServerConnectionHandler", "Error listening to Socket connections: " + reason);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method for stopping the WebSocket server.
|
||||
*/
|
||||
public void stop() {
|
||||
try {
|
||||
this.serverSocket.close();
|
||||
this.forceClose.set(true);
|
||||
} 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(Consumer<String> 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();
|
||||
}
|
||||
}
|
@@ -1,150 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
@@ -1,35 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
@@ -1,217 +0,0 @@
|
||||
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.");
|
||||
}
|
||||
}
|
@@ -1,36 +0,0 @@
|
||||
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