Merge branch 'main' of ssh://gitlab.fdmci.hva.nl/propedeuse-hbo-ict/onderwijs/2023-2024/out-a-se-ti/blok-4/muupooviixee66

This commit is contained in:
SebasKoedam
2024-06-05 12:23:33 +02:00
15 changed files with 375 additions and 229 deletions

View File

@@ -1,12 +1,12 @@
package com.example.fitbot.exercise;
import com.example.fitbot.util.path.GesturePath;
import com.example.fitbot.util.path.AnglePath;
public class Exercise {
public final EMuscleGroup muscleGroup;
public final GesturePath leftPath;
public final GesturePath rightPath;
public final AnglePath leftPath;
public final AnglePath rightPath;
public final String name;
public final String shortDescription;
public final String description;
@@ -26,7 +26,10 @@ public class Exercise {
* @param imageUrl The URL of the image.
* @param videoUrl The URL of the video.
*/
public Exercise(EMuscleGroup muscleGroup, String name,String shortDescription, String description, String imageUrl, String videoUrl, GesturePath leftPath, GesturePath rightPath, float exerciseTimeInSeconds) {
public Exercise(EMuscleGroup muscleGroup, String name, String shortDescription,
String description, String imageUrl, String videoUrl,
AnglePath leftPath, AnglePath rightPath, float exerciseTimeInSeconds) {
this.name = name;
this.muscleGroup = muscleGroup;
this.shortDescription = shortDescription;

View File

@@ -1,6 +1,6 @@
package com.example.fitbot.exercise;
import com.example.fitbot.util.path.GesturePath;
import com.example.fitbot.util.path.AnglePath;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
@@ -28,9 +28,6 @@ public class ExerciseManager {
private static final String PROPERTY_PATH = "path";
private static final String PROPERTY_NAME = "name";
// The delimiter used to separate the paths of the sensors.
public static final String PATH_DELIMITER = ":";
public static final int SENSOR_COUNT = 2;
private static final String[] REQUIRED_PROPERTIES = {
@@ -40,8 +37,8 @@ public class ExerciseManager {
};
public static final int DEFAULT_EXERCISE_REPETITIONS = 10;
public static final float DEFAULT_SEGMENT_SPEED = 1.0f;
public static final float EXERCISE_ERROR_MARGIN = 1.0f;
public static final float EXERCISE_TIME_SCALING_FACTOR = 1.0f;
/**
* Function for sending an HTTP request to the server.
@@ -104,9 +101,10 @@ public class ExerciseManager {
// If one wants to add support for more sensors, one will have to adjust the Exercise
// class to support more paths.
System.out.println(content.get(PROPERTY_PATH).getAsString());
String[] leftRightData = content.get(PROPERTY_PATH).getAsString().split(PATH_DELIMITER);
if (leftRightData.length != SENSOR_COUNT) {
AnglePath[] paths = AnglePath.fromString(content.get(PROPERTY_PATH).getAsString());
if (paths.length != SENSOR_COUNT) {
System.out.println("Invalid path data.");
return null;
}
@@ -118,9 +116,9 @@ public class ExerciseManager {
content.get(PROPERTY_DESC).getAsString(),
content.get(PROPERTY_IMAGE_URL).getAsString(),
content.get(PROPERTY_VIDEO_URL).getAsString(),
GesturePath.fromString(leftRightData[0]),
GesturePath.fromString(leftRightData[1]),
DEFAULT_SEGMENT_SPEED
paths[0],
paths[1],
content.get(PROPERTY_EXERCISE_DURATION).getAsInt()
);
} catch (Exception e) {
e.printStackTrace();

View File

@@ -126,7 +126,7 @@ public class FitnessActivity extends RobotActivity implements RobotLifecycleCall
exerciseStatusElement.post(() -> {
this.fetchExerciseAsync((exercise) -> {
// Acquire paths from the exercise and provide them to the motion processor
Vector3f[][] vectors = new Vector3f[][]{exercise.leftPath.getVectors(), exercise.rightPath.getVectors()};
Vector3f[][] vectors = new Vector3f[][]{exercise.leftPath.getAngleVectors(), exercise.rightPath.getAngleVectors()};
motionProcessor = new InputProcessor(vectors, exercise.exerciseTimeInSeconds, SENSOR_SAMPLE_RATE);

View File

@@ -16,6 +16,7 @@ import android.view.WindowManager;
import android.widget.Button;
import com.example.fitbot.R;
import com.example.fitbot.pepper.Pepper;
import com.example.fitbot.util.NavigationManager;
import java.io.OutputStream;

View File

@@ -0,0 +1,67 @@
package com.example.fitbot.util.path;
import com.example.fitbot.exercise.ExerciseManager;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import org.joml.Vector3f;
public class AnglePath {
// The angles
private final Vector3f[] angles;
/**
* Create a new gesture path with a given set of vectors and curvature.
*
* @param angles The vectors that make up the path.
*/
public AnglePath(Vector3f[] angles) {
this.angles = angles;
}
/**
* Method for retrieving the vectors of the GesturePath.
*/
public Vector3f[] getAngleVectors() {
return this.angles;
}
/**
* Function for converting a string to a GesturePath object.
* This function has been updated to convert Json strings to AnglePath objects.
* The JSON must be in the following format:
* [
* { "deviceId": number, "data": [ [x, y, z], [x, y, z], ... ] },
* ]
*
* @param jsonInput The string to convert
* @return The AnglePath object
*/
public static AnglePath[] fromString(String jsonInput) {
if (jsonInput == null)
throw new IllegalArgumentException("Input string cannot be null");
JsonElement parsed = JsonParser.parseString(jsonInput);
if (!parsed.isJsonArray())
throw new IllegalArgumentException("Input string must be a JSON array");
if ( parsed.getAsJsonArray().size() != ExerciseManager.SENSOR_COUNT)
throw new IllegalArgumentException("Input string must contain 2 elements");
Vector3f[][] angles = new Vector3f[ExerciseManager.SENSOR_COUNT][];
for ( int dataArrayIdx = 0; dataArrayIdx < parsed.getAsJsonArray().size(); dataArrayIdx++)
{
JsonArray array = parsed.getAsJsonArray().get(dataArrayIdx).getAsJsonObject().get("data").getAsJsonArray();
angles[dataArrayIdx] = new Vector3f[array.size()];
for (int i = 0; i < array.size(); i++) {
JsonArray vec = array.get(i).getAsJsonArray();
angles[dataArrayIdx][i] = new Vector3f(vec.get(0).getAsFloat(), vec.get(1).getAsFloat(), vec.get(2).getAsFloat());
}
}
return new AnglePath[] {new AnglePath(angles[0]), new AnglePath(angles[1])};
}
}

View File

@@ -1,126 +0,0 @@
package com.example.fitbot.util.path;
import org.joml.Vector3f;
public class GesturePath {
// The vectors that make up the path.
private final PathSegment[] segments;
/**
* Create a new gesture path with a given set of vectors and curvature.
*
* @param vectors The vectors that make up the path.
*/
public GesturePath(Vector3f[] vectors) {
if (vectors.length < 2) {
this.segments = new PathSegment[1];
this.segments[0] = new PathSegment(
new Vector3f(0, 0, 0),
new Vector3f(0, 0, 0)
);
return;
}
this.segments = new PathSegment[vectors.length - 1];
for (int i = 0; i < vectors.length - 1; i++)
segments[i] = new PathSegment(vectors[i], vectors[i + 1]);
}
/**
* Constructor for a GesturePath with provided PathSegments.
*
* @param segments The PathSegments to initialize the path with.
*/
public GesturePath(PathSegment... segments) {
this.segments = segments;
}
/**
* Getter method for retrieving the path segments of this GesturePath.
*
* @return The path segments.
*/
public PathSegment[] getSegments() {
return segments;
}
/**
* Method for retrieving the vectors of the GesturePath.
*/
public Vector3f[] getVectors() {
Vector3f[] vectors = new Vector3f[segments.length + 1];
vectors[0] = segments[0].getStart();
for (int i = 0; i < segments.length; i++)
vectors[i + 1] = segments[i].getEnd();
return vectors;
}
/**
* Method for retrieving the closest path segment to a reference point.
*
* @param reference The reference point to find the closest path segment to.
* @return The closest path segment to the reference point.
*/
public PathSegment closest(Vector3f reference) {
// If there's only one segment, return that one.
if (segments.length == 1)
return segments[0];
PathSegment closest = segments[0];
for (int i = 1; i < segments.length; i++)
closest = PathSegment.closer(closest, segments[i], reference);
return closest;
}
/**
* 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(Vector3f referencePoint) {
return closest(referencePoint).difference(referencePoint); // Get the closest segment and calculate the error.
}
/**
* Function for converting a string to a GesturePath object.
* The input string bytes will be directly converted into 3d vectors.
* Every scalar is composed of 32 bits (4 characters), meaning 96 bits per vector.
* <p>
* Note: ASCII to Vector conversion is done in Big Endian format (most significant byte first).
*
* @param input The string to convert
* @return The GesturePath object
*/
public static GesturePath fromString(String input) {
if (input == null)
throw new IllegalArgumentException("Input string cannot be null");
byte[] bytes = input.getBytes();
// Check if the input string contains a valid amount of bytes (12 bytes per vector)
if (input.length() % 12 != 0) {
throw new IllegalArgumentException("Invalid input string length (" + input.length() + " bytes provided - must be a multiple of 12)");
}
Vector3f[] vectors = new Vector3f[input.length() / 12];
float[] xyz = new float[3];
for (int i = 0; i < bytes.length; i += 12) {
for (int j = 0; j < 3; j++) {
xyz[j] = Float.intBitsToFloat(
(bytes[i + j * 4] & 0xFF) << 24 |
(bytes[i + j * 4 + 1] & 0xFF) << 16 |
(bytes[i + j * 4 + 2] & 0xFF) << 8 |
(bytes[i + j * 4 + 3] & 0xFF)
);
}
vectors[i / 12] = new Vector3f(xyz[0], xyz[1], xyz[2]);
}
return new GesturePath(vectors);
}
}

View File

@@ -2,12 +2,15 @@ package com.example.fitbot.util.processing;
import android.util.Log;
import com.aldebaran.qi.sdk.object.geometry.Vector3;
import com.example.fitbot.exercise.Exercise;
import com.example.fitbot.exercise.ExerciseManager;
import com.example.fitbot.util.server.WebServer;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.JsonPrimitive;
import org.jetbrains.annotations.NotNull;
import org.joml.Vector3f;
@@ -17,12 +20,15 @@ import java.util.List;
public class InputProcessor {
private Vector3f[][] selfRotationVectorPaths; // Relative path of the motion data
private List<Vector3f>[] selfRotationVectorPaths; // Relative path of the motion data
//private Vector3f[][] selfRotationVectorPaths; // Relative path of the motion data
private Vector3f[][] targetRotationVectorPaths; // Target path of the motion data
private final float sampleRate; // The sample rate of the motion sensor
private float exerciseDurationInSeconds;
private float exerciseScore = 0.0F;
/**
* This field is used to determine if the motion data is being recorded.
* If this is the case, instead of functioning normally, the element
@@ -45,7 +51,7 @@ public class InputProcessor {
private IInputHandler motionDataConsumer;
private static final String[] REQUIRED_SENSOR_JSON_PROPERTIES =
{"rotationX", "rotationY", "rotationZ", "type", "deviceId"};
{"rotationX", "rotationY", "rotationZ", "deviceId"};
// The web server that listens for incoming motion data.
private WebServer server;
@@ -60,7 +66,9 @@ public class InputProcessor {
* @param inputSampleRate The sample rate of the motion sensor.
*/
public InputProcessor(Vector3f[][] paths, float exerciseTime, float inputSampleRate) {
selfRotationVectorPaths = new Vector3f[paths.length][(int) (exerciseTime * inputSampleRate)];
this.selfRotationVectorPaths = new ArrayList[2];
this.selfRotationVectorPaths[0] = new ArrayList<>();
this.selfRotationVectorPaths[1] = new ArrayList<>();
targetRotationVectorPaths = paths;
this.sampleRate = inputSampleRate;
@@ -78,10 +86,11 @@ public class InputProcessor {
if ( this.recordingMovement )
throw new IllegalStateException("Cannot change exercise while recording movement.");
this.selfRotationVectorPaths = new Vector3f[2][(int) (exercise.exerciseTimeInSeconds * this.sampleRate)];
this.targetRotationVectorPaths = new Vector3f[2][exercise.rightPath.getVectors().length];
this.targetRotationVectorPaths[0] = exercise.leftPath.getVectors();
this.targetRotationVectorPaths[1] = exercise.rightPath.getVectors();
this.selfRotationVectorPaths[0] = new ArrayList<>();
this.selfRotationVectorPaths[1] = new ArrayList<>();
this.targetRotationVectorPaths = new Vector3f[2][exercise.rightPath.getAngleVectors().length];
this.targetRotationVectorPaths[0] = exercise.leftPath.getAngleVectors();
this.targetRotationVectorPaths[1] = exercise.rightPath.getAngleVectors();
this.exerciseDurationInSeconds = exercise.exerciseTimeInSeconds;
this.secondsPassed = 0.0D;
this.lastTime = System.currentTimeMillis();
@@ -101,7 +110,8 @@ public class InputProcessor {
if (recording) {
this.secondsPassed = 0.0D;
this.lastTime = System.currentTimeMillis();
this.selfRotationVectorPaths = new Vector3f[2][(int) (duration * this.sampleRate)];
this.selfRotationVectorPaths[0] = new ArrayList<>();
this.selfRotationVectorPaths[1] = new ArrayList<>();
}
}
@@ -161,7 +171,8 @@ public class InputProcessor {
try {
Log.i("MotionProcessor", "Time passed: " + this.secondsPassed + "s");
Log.i("MotionProcessor", "Recording: " + this.recordingMovement + ", " + this.secondsPassed + " / " + this.recordingDurationInSeconds);
if ( this.recordingMovement)
Log.i("MotionProcessor", this.secondsPassed + " / " + this.recordingDurationInSeconds);
Log.i("MotionProcessor", "Received packet data: " + data);
JsonElement json = JsonParser.parseString(data);
@@ -178,9 +189,11 @@ public class InputProcessor {
}
// Parse the data
Vector3f rotation = new Vector3f(object.get("rotationX").getAsFloat(), object.get("rotationY").getAsFloat(), object.get("rotationZ").getAsFloat());
Vector3f rotation = new Vector3f(
object.get("rotationX").getAsFloat(),
object.get("rotationY").getAsFloat(),
object.get("rotationZ").getAsFloat());
int deviceId = object.get("deviceId").getAsInt();
String type = object.get("type").getAsString();
// Parse the retrieved data
parseRotationVector(rotation, deviceId);
@@ -203,7 +216,7 @@ public class InputProcessor {
// Supposed index of the current rotation vector in the `rotationVectorPaths` array
int selfIndex = (int) (secondsPassed * sampleRate);
this.selfRotationVectorPaths[deviceId].add(rotation);
if ( this.recordingMovement && this.secondsPassed >= this.recordingDurationInSeconds) {
// Do something with the recorded data.
@@ -217,11 +230,8 @@ public class InputProcessor {
Log.i("MotionProcessor", "Converted data: ");
Log.i("MotionProcessor", converted);
}
motionDataConsumer.accept(rotation, deviceId);
if (selfIndex >= selfRotationVectorPaths[deviceId].length || selfIndex < 0)
return;
selfRotationVectorPaths[deviceId][selfIndex] = rotation;
motionDataConsumer.accept(rotation, deviceId);
}
}
@@ -234,38 +244,36 @@ public class InputProcessor {
*/
private String convertRecordedDataToString()
{
// First, remove empty entries
StringBuilder pathBuilder = new StringBuilder();
int[] intBits = new int[3];
char[] vectorChars = new char[12]; // 4 bytes per scalar, 12 chars per vector
JsonArray jsonArray = new JsonArray();
/*
* Convert to JSON array in the following format:
* [
* { "deviceId": number, "data": [ [x, y, z], [x, y, z], ... ] },
* ]
*/
// Iterate over all devices. In the current instance, it's 2.
for ( int deviceId = 0; deviceId < selfRotationVectorPaths.length; deviceId++) {
for (Vector3f dataPoint : selfRotationVectorPaths[deviceId]) {
if (dataPoint != null) {
// Convert float to int bits for conversion to char
intBits[0] = Float.floatToIntBits(dataPoint.x);
intBits[1] = Float.floatToIntBits(dataPoint.y);
intBits[2] = Float.floatToIntBits(dataPoint.z);
JsonObject jsonDeviceObject = new JsonObject();
jsonDeviceObject.addProperty("deviceId", deviceId);
// Convert int bits to char, in Big Endian order.
// This is important for converting back to float later.
for (int i = 0; i < 3; i++) {
vectorChars[i * 4] = (char) (intBits[i] >> 24);
vectorChars[i * 4 + 1] = (char) (intBits[i] >> 16);
vectorChars[i * 4 + 2] = (char) (intBits[i] >> 8);
vectorChars[i * 4 + 3] = (char) intBits[i];
}
// Data array
JsonArray jsonDeviceDataArray = new JsonArray();
pathBuilder.append(vectorChars);
for ( Vector3f vector : selfRotationVectorPaths[deviceId]) {
JsonArray jsonScalarArray = new JsonArray();
jsonScalarArray.add(vector.x);
jsonScalarArray.add(vector.y);
jsonScalarArray.add(vector.z);
jsonDeviceDataArray.add(jsonScalarArray);
}
jsonDeviceObject.add("data", jsonDeviceDataArray);
jsonArray.add(jsonDeviceObject);
}
// Add a separator between devices
if ( deviceId < selfRotationVectorPaths.length - 1)
pathBuilder.append(ExerciseManager.PATH_DELIMITER);
}
return pathBuilder.toString();
return jsonArray.toString();
}
/**
@@ -288,6 +296,13 @@ public class InputProcessor {
this.motionDataConsumer = consumer;
}
/**
* Function for getting the combined (average) error value of both sensors.
public double getCombinedError()
{
}*/
/**
* Function for getting the error offsets of the user's path compared to the
* target path at a given point in time.
@@ -300,7 +315,7 @@ public class InputProcessor {
*/
public double getError(int sensorId, float time) {
// Ensure the sensor ID is within the bounds of the array
/*// Ensure the sensor ID is within the bounds of the array
if (sensorId < 0 || sensorId >= selfRotationVectorPaths.length)
return 0.0d;
@@ -316,7 +331,7 @@ public class InputProcessor {
this.targetRotationVectorPaths[sensorId][targetIndex] != null
) {
return this.selfRotationVectorPaths[sensorId][selfIndex].distance(this.targetRotationVectorPaths[sensorId][targetIndex]);
}
}*/
return 0.0d;
}

View File

@@ -1,37 +0,0 @@
package com.example.fitbot;
import static org.junit.Assert.assertEquals;
import com.example.fitbot.util.path.GesturePath;
import com.example.fitbot.util.path.PathSegment;
import org.joml.Vector3f;
import org.junit.Test;
public class PathSegmentTest {
@Test
public void testPathSegment() {
// Test the PathSegment class
Vector3f[] vectors = new Vector3f[2];
vectors[0] = new Vector3f(0, 0, 0);
vectors[1] = new Vector3f(1, 1, 1);
GesturePath path = new GesturePath(vectors);
PathSegment[] segments = path.getSegments();
assertEquals(1, segments.length);
assertEquals(new Vector3f(0, 0, 0), segments[0].getStart());
assertEquals(new Vector3f(1, 1, 1), segments[0].getEnd());
assertEquals(new Vector3f(0.5f, 0.5f, 0.5f), segments[0].interpolate(0.5));
}
@Test
public void test_pathSegmentInterpolation() {
Vector3f start = new Vector3f(0, 0, 0);
Vector3f end = new Vector3f(1, 1, 1);
PathSegment segment = new PathSegment(start, end);
assertEquals(new Vector3f(0.5f, 0.5f, 0.5f), segment.interpolate(0.5));
}
}

View File

@@ -20,8 +20,9 @@ classDiagram
Raspberry Pi --> NodeJS
Raspberry Pi --> Database
NodeJS <--> Android Application : Request exercise data from database
NodeJS <--> Android Application : Request exercise data from database. Send ip adress to cache
Database <--> NodeJS : Database queries
NodeJS --> ESP8266 : Get pepper ip
ESP8266 --> Android Application : Send rotation data via WiFi to\n Pepper Web Server
@@ -48,6 +49,7 @@ namespace Server {
class NodeJS {
+MariaDB
+Handle requests
+Cache pepper IP
}
}

View File

@@ -0,0 +1,88 @@
## Pepper Abstraction Design
---
### Introduction
The Pepper robot is a complex system that can be controlled by a variety of different actions. To make the system more
manageable, we've decided implement abstraction and encapsulation in the classes related to Pepper controls.
This way, we can easily add new action events in the future.
All these classes inherit from the `AbstractPepperActionEvent` class.
### Problems
1. The Pepper robot functions with a system that only allows one action to be executed at a time, per action category.
This means that, for example, when two speech actions are executed at the same time, the application will crash due
to a `RuntimeException` being thrown. Due to this fact, whenever the execution of multiple processes overlap,
the application will crash.
2. Besides the first problem, for the Pepper robot to be able to execute any actions, it is required to have a
QiContext available. This context is only provided in a class that extends the `RobotLifecycleCallbacks` class.
This means, that whenever the class does not extend this class, the robot will be unable to execute any actions.
### Solution
To prevent the application from crashing, we've decided to implement a queue system in the `Pepper` class.
This system allows us to queue any new actions that need to be executed whenever another action is already
being executed. This way, we can prevent the application throwing a `RuntimeException` and thus crashing.
To tackle the second problem, we've decided to implement a system where the Pepper class has a global variable, which
holds the current QiContext. This means, that whenever a user decides to execute an action, and no current QiContext
is available, the action will be queued until a QiContext is available. This means that we can queue several actions
at once without any exceptions being thrown.
### Diagrams
#### Class Diagram
```mermaid
classDiagram
class Pepper {
-pepperActionEventQueue : ConcurrentLinkedQueue<AbstractPepperActionEvent>
-isAnimating : AtomicBoolean
-isSpeaking : AtomicBoolean
+latestContext : QiContext
+addToEventQueue(AbstractPepperActionEvent event)
+provideQiContext(QiContext context)
-processEventQueue()
}
class AbstractPepperActionEvent {
+getAction() EPepperAction
}
class PepperSpeechEvent {
+phrase : String
+locale : Locale
+PepperSpeechEvent(String phrase, Locale locale)
+getSay(QiContext context) Say
}
class PepperAnimationEvent {
+PepperAnimationEvent(String animationName)
+PepperAnimationEvent(String animationName, IAnimationCompletedListener listener)
+getAnimation(QiContext context) Animate
+animationName : String
+IAnimationCompletedListener : IAnimationCompletedListener
}
Pepper <|-- AbstractPepperActionEvent
PepperSpeechEvent <|-- AbstractPepperActionEvent
PepperAnimationEvent <|-- AbstractPepperActionEvent
```
#### Queue System in Pepper class
```mermaid
graph LR
subgraph "Pepper Class - Action Queue System"
speak[say&#40String phrase&#41\nPublic\nCreate PepperSpeechEvent] --Call method--> addQueue
animate[animate&#40String animationName&#41\nPublic\nCreate PepperAnimationEvent] --Call method--> addQueue
addQueue[addToEventQueue&#40AbstractPepperActionEvent event&#41\nPublic\nAdd provided event to event queue] --Add to queue--> queue[Event Queue\nPrivate\nQueue containing all events that\nneed to be executed]
addQueue --Call method--> handleQueue[processEventQueue&#40&#41\nPrivate\nCheck whether there is a context\navailable, and whether an event\nis currently being executed.\nExecutes the next event in the Queue]
queue <.-> handleQueue
provideCtx[provideQiContext&#40QiContext context&#41\nPublic\nSets global QiContext variable\nto provided context. If the context \nis not null,process the event queue] --Sets global QiContext variable--> handleQueue
end
```

View File

@@ -1,9 +0,0 @@
## Expert review #1
### Document as you go
Documenteer alle problemen die voorkomen bij het project en noteer de
oplossingen voor deze problemen. Dit kan bijvoorbeeld d.m.v. een command die
cache files verwijderd, of op welke manier je een project fixt. Dit kan toekomstige
problemen voorkomen.

View File

@@ -12,6 +12,9 @@ Deze classes inheriten `AbstractPepperActionEvent`.
K2: Gebruikers Test
( Maak een fictief persoon die de applicatie zou kunnen gebruiken, om erachter te komen
wat ze zouden willen )
---
Wij hebben gezamenlijk gecommuniceerd over het plan, echter is alleen
@@ -32,6 +35,14 @@ Zie bestand 'infrastructure.md'
K4 - Ontwerp embedded system
Documenteer het queue systeem van de Pepper class
Maak een mermaid graph LR diagram
---
Zie '/documentation/hardware/sensors'
K5 - Embedded Software Schrijven
Feedback:
- Is in principe K1,

View File

@@ -0,0 +1,72 @@
### Gebruikersonderzoek Personage
---
Gezien de huidige omstandigheden is het nogal lastig om actuele feedback te verkrijgen
van onze doelgroep. Dit betekent dat we een fictief karakter moeten ontwikkelen die als onze
gebruiker functioneert. Hierbij moeten we zo veel mogelijk parameters vaststellen die
overeen kunnen komen met een potentiele gebruiker.
Om hiermee te beginnen is het noodzakelijk om eerst deze parameters vast te stellen.
Deze zijn als volgt:
- Bedraagt een leeftijd van tussen de 50 en 70 jaar
- Woont in een verzorgingstehuis
- Heeft enigzins last van eenzaamheid
- Heeft moeite met lichamelijke activiteiten
- Grote kans op slecht zicht
Nu deze parameters vastgesteld zijn kunnen we een fictief personage ontwikkelen die als onze
gebruiker functioneert. Dit personage zal de naam 'Henk' dragen.
Vervolgens is het handig om de applicatie te introduceren.
De applicatie zal als volgt geintroduceerd worden:
"Onze applicatie is een interactief programma waarmee u samen fitness activiteiten kunt
verrichten met een virtuele assistent genaamd Pepper. Pepper zal u begeleiden door de
verschillende oefeningen en u helpen met het uitvoeren van de oefeningen. Iedere
oefening zal worden begeleid door een stem die u verteld wat u moet doen en hoe u dit
moet doen. Daarnaast zal Pepper u ook feedback geven over hoe goed u de oefeningen
uitvoert."
Een fictieve reactie op alle gestelde voorwaarden kan zijn als volgt:
#### Introductie
Goedendag, ik ben Henk en ik ben 67 jaar oud. Ik woon al een aantal jaar in verzorgingstehuis genaamd Amstelhuis.
Helaas heb ik de laatste tijd wat meer last van mijn gezondheid, waardoor ik minder goed kan bewegen. Hierdoor voel ik
me soms wat eenzaam en verveel ik me wel eens.
Ik ben altijd erg actief geweest, dus toen ik hoorde over de Pepper FitBot app was ik meteen enthousiast. De
verpleegster vertelde me dat de Pepper app misschien wel kan helpen om mijn conditie op peil te houden en om me minder
eenzaam te voelen. Dus ik ben erg benieuwd wat de FitBot app allemaal kan!
#### Gebruikservaring
Eerste indruk: Toen ik de FitBot app voor het eerst gebruikte, vond ik het erg leuk dat Pepper me begeleidde door de
oefeningen. Hij is duidelijk en behulpzaam, en hij maakt er een gezellige sfeer van.
##### Gebruiksgemak
De FitBot app is erg gebruiksvriendelijk. De app is eenvoudig te bedienen en de instructies zijn
duidelijk. Ook vind ik het fijn dat de app mijn voortgang bijhoudt.
##### Functionaliteit
De FitBot app heeft veel leuke en nuttige functies. Ik vind het vooral leuk dat er verschillende
oefeningen zijn voor verschillende niveaus.
##### Motivatie
Pepper is een geweldige motivator. Hij moedigt me aan om door te gaan en hij geeft me complimenten als ik
goed bezig ben. Hierdoor ben ik gemotiveerder om te blijven sporten.
##### Verbeterpunten
- Het zou leuk zijn als er meer oefeningen aan de app worden toegevoegd. Zo zou ik graag meer oefeningen willen doen voor
mijn spierkracht en lenigheid.
- De uitleg van de oefeningen is soms een beetje kort. Het zou fijn zijn als deze wat uitgebreider zou zijn.
- Het zou leuk zijn als er de mogelijkheid was om samen met anderen te sporten via de app. Zo zou ik bijvoorbeeld met
mijn kleinkinderen kunnen videobellen terwijl we allebei dezelfde oefeningen doen.
Al met al ben ik erg tevreden over de FitBot app. Het is een leuke en effectieve manier om te sporten. Pepper is een
fijne motivator en de app heeft veel nuttige functies. Ik zou de FitBot app zeker aanbevelen aan andere mensen in het
verzorgingstehuis.

View File

@@ -0,0 +1,61 @@
### Infrastructure Design
---
As for our project, we've made the following design choices for our infrastructure.
We've decided to implement a NodeJS server on a Raspberry Pi, which will handle the requests for retrieving exercises.
This server will communicate with a MariaDB database, which contains the exercise data.
The Pepper robot will host a web server, which will handle the incoming rotational data from an ESP8266.
This data will then be processed by a motion processor class, `InputProcessor`, which will compare the rotational data
to the data of the current exercise and show how well the user is performing.
Down below is a visual representation of how this infrastructure will look like.
### General Infrastructure Diagram
```mermaid
graph TB
subgraph "Raspberry Pi"
server[NodeJS Server\n\nHandles requests for\nretrieving exercises]
db[Database - MariaDB\n\nContains exercise data]
server --Fetch database entry--> db
db --Return retrieved entry--> server
end
subgraph "Pepper Robot"
webServer[Web Server\n\nHandles incoming rotational data\nfrom ESP8266]
motionProcessor[Motion Processor\n\nProcesses rotational data,\ncompares it to the current exercise\nand shows the statistics on the screen]
ui[User Interface\n\nShows the current exercise,\nhow to perform it and the\nstatistics of the user's performance]
motionProcessor --Send HTTP GET for Exercise--> server
server --Send exercise data\nin JSON format--> motionProcessor
webServer --Process rotational data--> motionProcessor
motionProcessor --Show statistics\non the UI--> ui
end
subgraph "Motion Sensing Device"
esp[ESP8266\n\nMeasures sensor data\nand sends it to the web server]
gyro[Gyroscope\n\nMeasures rotational data\n&#40Rx, Ry, Rz&#41]
esp --Send rotational data\nto Pepper Web Server--> webServer
gyro <---> esp
end
```
### Database Diagram
For the design of our database, we've decided to only add a single table named `Exercise`.
This table contains all the information needed for the exercises.
```mermaid
classDiagram
class Exercise {
+ExerciseId : INT
+Name : VARCHAR
+Description : VARCHAR
+ShortDescription : VARCHAR
+ImageURL : VARCHAR
+VideoURL : VARCHAR
+MuscleGroup : VARCHAR
+Path : VARCHAR
}
```