Merge remote-tracking branch 'origin/main'

This commit is contained in:
Luca Warmenhoven
2024-06-03 14:04:03 +02:00
109 changed files with 2875 additions and 1570 deletions

View File

@@ -1,3 +1,3 @@
{
"java.configuration.updateBuildConfiguration": "interactive"
"java.configuration.updateBuildConfiguration": "disabled"
}

View File

@@ -4,28 +4,52 @@ void Connectivity::connectWiFi(char* ssid, char* pass){
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, pass);
while (WiFi.status() != WL_CONNECTED) {
Serial.println("connecting to wifi");
delay(1000);
}
Serial.println(WiFi.localIP());
}
void Connectivity::websocketSetup(char* ip, uint16_t port, char* adress){
//ws server address, port and URL
webSocket.begin(ip , port, adress);
// try every 500 again if connection has failed
webSocket.setReconnectInterval(500);
// void Connectivity::websocketSetup(char* ip, uint16_t port, char* adress){
// //ws server address, port and URL
// webSocket.begin(ip , port, adress);
// // try every 500 again if connection has failed
// webSocket.setReconnectInterval(500);
// }
// void Connectivity::sendData(float roll, float pitch, float yaw){
// String message = "{\"Sensor\": 1, \"roll\":\"" + String(roll) + "\",\"pitch\":\"" + String(pitch) + "\",\"yaw\":\"" + String(yaw) + "\"}";
// webSocket.sendTXT(message);
// }
const char* getServerURL = "http://145.92.8.132:443/get-ip";
String ipAddress = "";
String Connectivity::fetchIPAddress() {
if (WiFi.status() == WL_CONNECTED) {
HTTPClient http;
WiFiClient client;
http.begin(client, getServerURL);
int httpCode = http.GET();
if (httpCode > 0) {
if (httpCode == HTTP_CODE_OK) {
ipAddress = http.getString();
}
} else {
Serial.printf("GET request failed, error: %s\n", http.errorToString(httpCode).c_str());
}
void Connectivity::sendData(float roll, float pitch, float yaw){
String message = "{\"Sensor\": 1, \"roll\":\"" + String(roll) + "\",\"pitch\":\"" + String(pitch) + "\",\"yaw\":\"" + String(yaw) + "\"}";
webSocket.sendTXT(message);
http.end();
} else {
Serial.println("WiFi not connected");
}
return ipAddress; // Add this return statement
}
/** Send a POST request to a server with provided data */
int Connectivity::httpPost(const char *serverAddress, const char *serverSubPath, const unsigned short serverPort,
const char *data, const size_t dataLength, const char *contentType)
{
WiFiClient wifi_client; // Ensure WiFiClient is declared and initialized
if (wifi_client.connect(serverAddress, serverPort)) {
wifi_client.printf("POST %s HTTP/1.1\r\n", serverSubPath);
wifi_client.printf("Content-Type: %s\r\n", contentType);

View File

@@ -5,6 +5,7 @@
#include <WebSocketsClient.h>
#include <ArduinoWiFiServer.h>
#include <ESP8266WiFi.h>
#include <ESP8266HTTPClient.h>
#include <ESP8266WiFiGeneric.h>
#include <ESP8266WiFiMulti.h>
#include <ESP8266WiFiSTA.h>
@@ -18,12 +19,12 @@ public:
void websocketSetup(char* ip, uint16_t port, char* adress);
void sendData(float roll, float pitch, float yaw);
int httpPost(const char *serverAddress, const char *serverSubPath, const unsigned short serverPort, const char *data, const size_t dataLength, const char *contentType);
String fetchIPAddress();
private:
ESP8266WiFiMulti wifi;
WiFiClient wifi_client;
WebSocketsClient webSocket;
// WebSocketsClient webSocket;
};

View File

@@ -1,42 +1,40 @@
#include "headerFile.h"
// SensorManager::Rotation offset;
#define BUFFER_SIZE 1024
#define DEVICE_ID 1
char *buffer = (char *)malloc(sizeof(char) * BUFFER_SIZE);
void setup() {
Serial.begin(9600);
// Serial.println("startup");
//connect to internet and start sensor
connectivity.connectWiFi(ssid, pass);
sensorManager.sensorSetup();
Serial.begin(9600);
}
unsigned long lastTime = 0; // will store the last time the code was run
void loop() {
SensorManager::eulerAngles eulerRotation = sensorManager.getEulerAngles();
SensorManager::acceleration rotationAcceleration = sensorManager.getAcelleration();
unsigned long lastTime = 0; // will store the last time the code was run
// SensorManager::acceleration rotationAcceleration = sensorManager.getAcelleration();
struct acceleration {
float x = 9;
float y = 9;
float z = 9;
} accelData;
unsigned long currentTime = millis();
if (currentTime - lastTime >= 100) { // 100 ms has passed
memset(buffer, 0, BUFFER_SIZE);
sprintf(
buffer,
"{\"deviceId\": %d, \"rotationX\": %d, \"rotationY\": %d, \"rotationZ\": %d, \"accelerationX\": %d, \"accelerationY\": %d, \"accelerationZ\": %d, \"type\": %s}",
"{\"deviceId\": %d, \"rotationX\": %f, \"rotationY\": %f, \"rotationZ\": %f, \"accelerationX\": %f, \"accelerationY\": %f, \"accelerationZ\": %f, \"type\": %s}",
DEVICE_ID,
eulerRotation.roll,
eulerRotation.pitch,
eulerRotation.yaw,
rotationAcceleration.x,
rotationAcceleration.y,
rotationAcceleration.z,
accelData.x,
accelData.y,
accelData.z,
"data");
// Serial.println(connectivity.httpPost("192.168.137.45", "/", 3445, message.c_str(), message.length(), "json"));
// Serial.println(message);
connectivity.httpPost("192.168.137.45", "/", 3445, buffer, strlen(buffer), "application/json");
// %d = int, %f = floatation, %s = string
connectivity.httpPost(connectivity.fetchIPAddress(), "/", 3445, buffer, strlen(buffer), "application/json");
lastTime = currentTime;
}
}

View File

@@ -9,15 +9,12 @@ void SensorManager::sensorSetup() {
//wait for the sensor to start before continue
if (myIMU.begin() == false) {
delay(1000);
// Serial.println(".");
}
//start sensorfunction and start autocalibration
//once calibration is enabled it attempts to every 5 min
myIMU.enableGyroIntegratedRotationVector(100); //send data every 100ms
myIMU.enableAccelerometer(100); //Send data update every 100ms
Serial.println(F("magnetometer rotation enabled"));
myIMU.enableStepCounter(500); //Send data update every 500ms
// myIMU.enableAccelerometer(100); //Send data update every 100ms
// myIMU.enableStepCounter(500); //Send data update every 500ms
}
//get sensordata
SensorManager::RotationQuintillions SensorManager::getQuintillions() {

View File

@@ -8,6 +8,10 @@ Connectivity connectivity;
WebSocketsClient webSocket;
#define USE_SERIAL Serial
#define ssid "1235678i"
#define pass "12345678"
#define BUFFER_SIZE 1024
#define DEVICE_ID 1
#define IP_ADDRESS "192.168.137.12"
char *buffer = (char *)malloc(sizeof(char) * BUFFER_SIZE);

View File

@@ -1,33 +0,0 @@
#include <BLEDevice.h>
#include <BLEServer.h>
// Define the service UUID
#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
// Define the characteristic UUID
#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8"
void setup() {
// Create a BLE server
BLEServer *pServer = BLEDevice::createServer();
// Create a BLE service
BLEService *pService = pServer->createService(SERVICE_UUID);
// Create a BLE characteristic
BLECharacteristic *pCharacteristic = pService->createCharacteristic(
CHARACTERISTIC_UUID, BLECharacteristic::PROPERTY_READ);
// Set the characteristic value
pCharacteristic->setValue("Hello, Bluetooth!");
// Start the service
pService->start();
// Start advertising the service
pServer->getAdvertising()->start();
}
void loop() {
// Nothing to do here
}

View File

@@ -1,4 +1,3 @@
/**
*
* @param {Request} request The incoming request
@@ -14,7 +13,7 @@ function handleIncoming(request, response, app, pool)
if (!request.hasOwnProperty('uid') || typeof request.uid !== 'number')
{
query = 'SELECT * FROM Exercise';
query = 'SELECT * FROM Exercise ORDER BY RAND() LIMIT 1';
} else parameters.push(request.uid);
// Acquire database connection
@@ -33,13 +32,17 @@ function handleIncoming(request, response, app, pool)
// Send back the data in the right format
let converted = rows.map(row => {
return {
exerciseId: row.ExerciseID,
name: row.Name,
description: row.Description,
muscleGroup: row.MuscleGroup,
shortDescription: row.ShortDescription,
description: row.Description,
imageUrl: row.ImageURL,
videoUrl: row.VideoURL
videoUrl: row.VideoURL,
path: row.Path,
duration: row.Duration
};
});
})[0];
response
.status(200)

View File

@@ -0,0 +1,22 @@
const express = require('express');
const app = express();
app.use(express.json()); // for parsing application/json
let ipAddress = ''; // to store the received IP address
// endpoint to receive an IP address from an external source
app.post('/set-ip', (req, res) => {
ipAddress = req.body.ip;
console.log('IP address received:', ipAddress);
});
// endpoint for the ESP32 to fetch the IP address
app.get('/get-ip', (req, res) => {
res.json({ ip: ipAddress });
console.log('IP address sent to ESP32');
});
app.listen(42069, () => {
console.log('Server is running on port 42069');
});

View File

@@ -21,6 +21,20 @@ const pool = mariadb.createPool(databaseCredentials);
// Register incoming HTTP request handlers
require('./incoming_request_handlers')(app, pool);
let ipAddress = ''; // to store the received IP address
// endpoint to receive an IP address from an external source
app.post('/set-ip', (req, res) => {
ipAddress = req.body.ip;
console.log('IP address received:', ipAddress);
});
// endpoint for the ESP32 to fetch the IP address
app.get('/get-ip', (req, res) => {
res.json({ ip: ipAddress });
console.log('IP address sent to ESP32');
});
// Start server
app.listen(serverPort, () => {
console.log(`Server running on port ${serverPort}`);

View File

@@ -0,0 +1,8 @@
// config.js
module.exports = {
host: '127.0.0.1',
user: 'fitbot',
password: 'fitbot123',
database: 'fitbot',
port: 3306, // Default MariaDB port
};

View File

@@ -0,0 +1,35 @@
const express = require('express');
const mariadb = require('mariadb');
const config = require('./config');
const app = express();
const port = 443; // Use port 443
const pool = mariadb.createPool({
host: config.host,
user: config.user,
password: config.password,
database: config.database,
connectionLimit: 5,
acquireTimeout: 30000, // Increase timeout to 30 seconds
});
app.get('/', async (req, res) => {
let conn;
try {
conn = await pool.getConnection();
console.log("Connected to MariaDB!");
const rows = await conn.query("SELECT * FROM Exercise ORDER BY RAND() LIMIT 1");
console.log(rows); // [{val: 1}]
res.json(rows); // Send the result as JSON response
} catch (err) {
console.error("Unable to connect to MariaDB:", err);
res.status(500).send("Unable to connect to MariaDB");
} finally {
if (conn) conn.release(); // Release the connection back to the pool
}
});
app.listen(port, '145.92.8.132', () => {
console.log(`Server is listening on http://145.92.8.132:${port}`);
});

View File

@@ -16,25 +16,37 @@
<entry key="..\:/Users/Niels/muupooviixee66-3/code/src/Fitbot/app/src/main/res/layout/activity_end_screen.xml" value="0.165" />
<entry key="..\:/Users/Niels/muupooviixee66-3/code/src/Fitbot/app/src/main/res/layout/activity_endscreen.xml" value="0.1" />
<entry key="..\:/Users/Niels/muupooviixee66-3/code/src/Fitbot/app/src/main/res/layout/activity_fitness.xml" value="0.1234375" />
<entry key="..\:/Users/Niels/muupooviixee66-3/code/src/Fitbot/app/src/main/res/layout/activity_help.xml" value="0.1" />
<entry key="..\:/Users/Niels/muupooviixee66-3/code/src/Fitbot/app/src/main/res/layout/activity_main.xml" value="0.1" />
<entry key="..\:/Users/Niels/muupooviixee66-3/code/src/Fitbot/app/src/main/res/layout/header.xml" value="0.1234375" />
<entry key="..\:/Users/Niels/muupooviixee66-3/code/src/Fitbot/app/src/main/res/layout/toolbar.xml" value="0.1234375" />
<entry key="..\:/Users/sebas/Documents/HvA/Reposetories/muupooviixee66/code/src/Fitbot/app/src/main/res/drawable-v24/ic_launcher_foreground.xml" value="0.25" />
<entry key="..\:/Users/sebas/Documents/HvA/Reposetories/muupooviixee66/code/src/Fitbot/app/src/main/res/drawable/big_red_button_gradient.xml" value="0.2555" />
<entry key="..\:/Users/sebas/Documents/HvA/Reposetories/muupooviixee66/code/src/Fitbot/app/src/main/res/drawable/border_background.xml" value="0.2475" />
<entry key="..\:/Users/sebas/Documents/HvA/Reposetories/muupooviixee66/code/src/Fitbot/app/src/main/res/drawable/border_background_3.xml" value="0.2475" />
<entry key="..\:/Users/sebas/Documents/HvA/Reposetories/muupooviixee66/code/src/Fitbot/app/src/main/res/drawable/box_background.xml" value="0.2555" />
<entry key="..\:/Users/sebas/Documents/HvA/Reposetories/muupooviixee66/code/src/Fitbot/app/src/main/res/drawable/darkred_button_gradient.xml" value="0.346" />
<entry key="..\:/Users/sebas/Documents/HvA/Reposetories/muupooviixee66/code/src/Fitbot/app/src/main/res/drawable/help2_background.xml" value="0.2395" />
<entry key="..\:/Users/sebas/Documents/HvA/Reposetories/muupooviixee66/code/src/Fitbot/app/src/main/res/drawable/help_background.xml" value="0.2395" />
<entry key="..\:/Users/sebas/Documents/HvA/Reposetories/muupooviixee66/code/src/Fitbot/app/src/main/res/drawable/ic_baseline_home_48.xml" value="0.25" />
<entry key="..\:/Users/sebas/Documents/HvA/Reposetories/muupooviixee66/code/src/Fitbot/app/src/main/res/drawable/ic_baseline_settings_48.xml" value="0.25" />
<entry key="..\:/Users/sebas/Documents/HvA/Reposetories/muupooviixee66/code/src/Fitbot/app/src/main/res/drawable/ic_baseline_star_rate_48.xml" value="0.25" />
<entry key="..\:/Users/sebas/Documents/HvA/Reposetories/muupooviixee66/code/src/Fitbot/app/src/main/res/drawable/ic_launcher_background.xml" value="0.25" />
<entry key="..\:/Users/sebas/Documents/HvA/Reposetories/muupooviixee66/code/src/Fitbot/app/src/main/res/drawable/rectangle.xml" value="0.2395" />
<entry key="..\:/Users/sebas/Documents/HvA/Reposetories/muupooviixee66/code/src/Fitbot/app/src/main/res/drawable/red_button_gradient.xml" value="0.346" />
<entry key="..\:/Users/sebas/Documents/HvA/Reposetories/muupooviixee66/code/src/Fitbot/app/src/main/res/layout/activity_bicepvideo.xml" value="0.22826086956521738" />
<entry key="..\:/Users/sebas/Documents/HvA/Reposetories/muupooviixee66/code/src/Fitbot/app/src/main/res/layout/activity_main.xml" value="0.176" />
<entry key="..\:/Users/sebas/Documents/HvA/Reposetories/muupooviixee66/code/src/Fitbot/app/src/main/res/layout/activity_end_screen.xml" value="0.4" />
<entry key="..\:/Users/sebas/Documents/HvA/Reposetories/muupooviixee66/code/src/Fitbot/app/src/main/res/layout/activity_fitness.xml" value="0.1" />
<entry key="..\:/Users/sebas/Documents/HvA/Reposetories/muupooviixee66/code/src/Fitbot/app/src/main/res/layout/activity_help.xml" value="0.4" />
<entry key="..\:/Users/sebas/Documents/HvA/Reposetories/muupooviixee66/code/src/Fitbot/app/src/main/res/layout/activity_info_dialog.xml" value="0.264" />
<entry key="..\:/Users/sebas/Documents/HvA/Reposetories/muupooviixee66/code/src/Fitbot/app/src/main/res/layout/activity_main.xml" value="0.4" />
<entry key="..\:/Users/sebas/Documents/HvA/Reposetories/muupooviixee66/code/src/Fitbot/app/src/main/res/layout/activity_main_screen.xml" value="0.1" />
<entry key="..\:/Users/sebas/Documents/HvA/Reposetories/muupooviixee66/code/src/Fitbot/app/src/main/res/layout/activity_power_screen.xml" value="0.1" />
<entry key="..\:/Users/sebas/Documents/HvA/Reposetories/muupooviixee66/code/src/Fitbot/app/src/main/res/layout/activity_sport_item.xml" value="0.1" />
<entry key="..\:/Users/sebas/Documents/HvA/Reposetories/muupooviixee66/code/src/Fitbot/app/src/main/res/layout/activity_sport_menu.xml" value="0.1" />
<entry key="..\:/Users/sebas/Documents/HvA/Reposetories/muupooviixee66/code/src/Fitbot/app/src/main/res/layout/header.xml" value="0.125" />
<entry key="..\:/Users/sebas/Documents/HvA/Reposetories/muupooviixee66/code/src/Fitbot/app/src/main/res/layout/toolbar.xml" value="0.125" />
<entry key="..\:/Users/sebas/Documents/HvA/Reposetories/muupooviixee66/code/src/Fitbot/app/src/main/res/layout/dialog_info.xml" value="0.4" />
<entry key="..\:/Users/sebas/Documents/HvA/Reposetories/muupooviixee66/code/src/Fitbot/app/src/main/res/layout/header.xml" value="0.264" />
<entry key="..\:/Users/sebas/Documents/HvA/Reposetories/muupooviixee66/code/src/Fitbot/app/src/main/res/layout/toolbar.xml" value="0.264" />
<entry key="..\:/Users/sebas/Documents/HvA/Reposetories/muupooviixee66/code/src/Fitbot/app/src/main/res/menu/main_menu.xml" value="0.4" />
<entry key="app/src/main/res/layout/activity_end_screen.xml" value="0.1" />
<entry key="app/src/main/res/layout/activity_fitness.xml" value="0.23550724637681159" />
<entry key="app/src/main/res/layout/activity_main.xml" value="0.1" />

View File

@@ -38,9 +38,6 @@ dependencies {
implementation 'org.joml:joml:1.10.5'
implementation 'com.google.code.gson:gson:2.8.6'
testImplementation 'junit:junit:4.13.2'
testImplementation 'org.junit.jupiter:junit-jupiter'
testImplementation 'org.junit.jupiter:junit-jupiter'
testImplementation 'org.junit.jupiter:junit-jupiter'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
implementation 'com.aldebaran:qisdk:1.7.5'

View File

@@ -10,19 +10,21 @@
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-feature
android:name="android.hardware.bluetooth"
android:required="true" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:icon="@mipmap/fitbot_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:roundIcon="@mipmap/fitbot_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

View File

@@ -3,21 +3,30 @@ package com.example.fitbot.exercise;
public enum EMuscleGroup {
// TODO: Implement
TORSO(0),
ARMS(1),
LEGS(2),
BALANCE(3);
TORSO(0, new String[]{"upper body", "torso"}),
ARMS(1, new String[]{"arms", "arm", "shoulder"}),
LEGS(2, new String[]{"Lower body", "legs", "leg"});
int muscleGroupIdentifier;
String[] muscleGroupNames;
EMuscleGroup(int identifier) {
EMuscleGroup(int identifier, String[] muscleGroupNames) {
this.muscleGroupIdentifier = identifier;
this.muscleGroupNames = muscleGroupNames;
}
public int getIdentifier() {
return this.muscleGroupIdentifier;
}
public static EMuscleGroup parse(String name) {
for (EMuscleGroup muscleGroup : EMuscleGroup.values())
for (String muscleGroupName : muscleGroup.muscleGroupNames)
if (muscleGroupName.equalsIgnoreCase(name))
return muscleGroup;
return null;
}
public static EMuscleGroup parse(int identifier) {
for (EMuscleGroup muscleGroup : EMuscleGroup.values()) {
if (muscleGroup.getIdentifier() == identifier) {

View File

@@ -1,28 +1,17 @@
package com.example.fitbot.exercise;
import android.util.Log;
import com.example.fitbot.util.path.GesturePath;
import com.example.fitbot.util.processing.IMotionDataConsumer;
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 IWebServerHandler {
private EMuscleGroup muscleGroup;
private GesturePath leftPath;
private GesturePath rightPath;
private String title;
private String description;
private float segmentsPerSecond;
// Static fields.
private static WebServer webSocket;
private static Exercise currentExercise = null;
public class Exercise {
public final EMuscleGroup muscleGroup;
public final GesturePath leftPath;
public final GesturePath rightPath;
public final String title;
public final String description;
public final String imageUrl;
public final String videoUrl;
public final float exerciseTimeInSeconds;
/**
* Constructor for the AbstractExercise class.
@@ -32,106 +21,21 @@ public class Exercise implements IWebServerHandler {
* @param rightPath The path of the right hand.
* @param title The title of the exercise.
* @param description The description of the exercise.
* @param segmentsPerSecond The number of segments per second.
* This determines how fast the exercise should be performed.
* @param imageUrl The URL of the image.
* @param videoUrl The URL of the video.
*/
public Exercise(EMuscleGroup muscleGroup, String title, String description, GesturePath leftPath, GesturePath rightPath) {
public Exercise(EMuscleGroup muscleGroup, String title, String description, String imageUrl, String videoUrl, GesturePath leftPath, GesturePath rightPath, float exerciseTimeInSeconds) {
this.muscleGroup = muscleGroup;
this.title = title;
this.description = description;
this.leftPath = leftPath;
this.rightPath = rightPath;
this.imageUrl = imageUrl;
this.videoUrl = videoUrl;
this.exerciseTimeInSeconds = exerciseTimeInSeconds;
}
/**
* Start the exercise.
* This method starts a WebSocket server
*/
public final void startExercise() {
// Ensure no other exercise is active.
if (currentExercise != null && currentExercise != this) {
currentExercise.__stopExercise();
Log.i("Exercises", "Another exercise was started when another was still running.");
}
// If a WebSocket server is already running, change the event handler to be this class.
if (webSocket != null && webSocket.isConnected()) {
webSocket.setEventHandler(this);
}
try {
webSocket = WebServer.createServer();
Objects.requireNonNull(webSocket, "WebSocket server could not be created.");
webSocket.setEventHandler(this);
currentExercise = this;
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* Method for ending this exercise and returning the grade of the performance
* of this activity.
*/
public final double finishExercise() {
this.__stopExercise();
// TODO: Implement grade calculation
return 0.0;
}
/**
* Stop the exercise.
* This method stops the WebSocket server.
*/
private void __stopExercise() {
if (webSocket != null && webSocket.isConnected()) {
webSocket.stop();
webSocket = null;
}
currentExercise = null;
}
/**
* Check if the current exercise is the current activity.
*/
public final boolean isCurrentActivity() {
return currentExercise == this;
}
/**
* Get the muscle group of the exercise.
*/
public EMuscleGroup getMuscleGroup() {
return muscleGroup;
}
/**
* Get the path of the exercise.
*/
public GesturePath[] getPath() {
return new GesturePath[]{leftPath, rightPath};
}
public String getTitle() {
return title;
}
public String getDescription() {
return description;
}
/**
* Get the speed of the exercise.
*/
public double getSegmentsPerSecond() {
return segmentsPerSecond;
}
@Override
public void onReceive(String message) {
public interface ExerciseFetchHandler {
void handle(Exercise exercise);
}
}

View File

@@ -4,27 +4,45 @@ import com.example.fitbot.util.path.GesturePath;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import org.joml.Vector3f;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Constructor;
import java.net.URL;
import java.net.URLConnection;
public class ExerciseManager {
private static final String HOST_ADDRESS = "http://145.92.8.132";
private static final String HOST_ADDRESS = "http://145.92.8.132:443/";
// The value of these property variables must be equivalent of
// the JSON data that the database sends back.
// If this is not the case then the exercise retrieval will fail.
private static final String PROPERTY_EXERCISE_DURATION = "duration";
private static final String PROPERTY_EXERCISE_ID = "exerciseId";
private static final String PROPERTY_MUSCLE_GROUP = "muscleGroup";
private static final String PROPERTY_SHORT_DESC = "shortDescription";
private static final String PROPERTY_IMAGE_URL = "imageUrl";
private static final String PROPERTY_VIDEO_URL = "videoUrl";
private static final String PROPERTY_DESC = "description";
private static final String PROPERTY_VECTORS = "vector_data";
private static final String PROPERTY_PATH = "path";
private static final String PROPERTY_NAME = "name";
private static final String PROPERTY_MUSCLE_GROUP = "muscle_group";
private static final String PROPERTY_SEGMENT_SPEED = "segment_speed";
private static final float DEFAULT_SEGMENT_SPEED = 1.0f;
// 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 = {
PROPERTY_MUSCLE_GROUP, PROPERTY_DESC, PROPERTY_IMAGE_URL,
PROPERTY_VIDEO_URL, PROPERTY_NAME, PROPERTY_PATH,
PROPERTY_EXERCISE_DURATION, PROPERTY_EXERCISE_ID,
PROPERTY_SHORT_DESC
};
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;
/**
* Function for sending an HTTP request to the server.
@@ -33,16 +51,19 @@ public class ExerciseManager {
* @param method The method to use for the request, e.g. GET or POST.
* @param contentType The content type of the request.
* @param body The body of the request.
*
* @return The response from the server.
*/
private static String sendHTTP(String url, String method, String contentType, String body) {
public static String sendHTTP(String url, String method, String contentType, String body) {
try {
URLConnection connection = new URL(url).openConnection();
connection.setDoOutput(true);
connection.setDoInput(true);
connection.addRequestProperty("Content-Type", contentType);
connection.addRequestProperty("Request-Method", method);
connection.getOutputStream().write(body.getBytes());
connection.connect();
// Send a body if it is present
if (body != null)
connection.getOutputStream().write(body.getBytes());
InputStream stream = connection.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
StringBuilder builder = new StringBuilder();
@@ -60,23 +81,41 @@ public class ExerciseManager {
/**
* Function for retrieving an exercise from the Raspberry Pi Database.
*
* @param uniqueIdentifier The unique identifier of the exercise
* @return The exercise, if it exists on the server. Otherwise null.
*/
public static Exercise retrieveExercise(String uniqueIdentifier) {
public static Exercise fetchExerciseFromDatabase() {
String response = sendHTTP(
HOST_ADDRESS + "/acquire", "GET", "application/json", "{\"kind\":\"" + uniqueIdentifier + "\"}"
HOST_ADDRESS, "POST", "application/json",
);
// Validate the response
if (response != null) {
try {
JsonObject content = JsonParser.parseString(response).getAsJsonObject();
// Ensure all required properties are present
for (String property : REQUIRED_PROPERTIES) {
if (!content.has(property)) {
return null;
}
}
// Path data is split into two parts, due to the left and right hand.
// If one wants to add support for more sensors, one will have to adjust the Exercise
// class to support more paths.
String[] leftRightData = content.get(PROPERTY_PATH).getAsString().split(PATH_DELIMITER);
if (leftRightData.length != SENSOR_COUNT)
return null;
return new Exercise(
EMuscleGroup.parse(content.get(PROPERTY_MUSCLE_GROUP).getAsInt()),
EMuscleGroup.parse(content.get(PROPERTY_MUSCLE_GROUP).getAsString()),
content.get(PROPERTY_NAME).getAsString(),
content.get(PROPERTY_DESC).getAsString(),
gesturePathFromString(content.get(PROPERTY_VECTORS).getAsString()),
gesturePathFromString(content.get(PROPERTY_SEGMENT_SPEED).getAsString())
content.get(PROPERTY_IMAGE_URL).getAsString(),
content.get(PROPERTY_VIDEO_URL).getAsString(),
GesturePath.fromString(leftRightData[0]),
GesturePath.fromString(leftRightData[1]),
DEFAULT_SEGMENT_SPEED
);
} catch (Exception e) {
e.printStackTrace();
@@ -84,42 +123,4 @@ public class ExerciseManager {
}
return null;
}
/**
* 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.
*
* 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
*/
private static GesturePath gesturePathFromString(String input) {
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");
}
GesturePath.Builder builder = new GesturePath.Builder();
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)
);
}
builder.addVector(new Vector3f(
xyz[0], xyz[1], xyz[2]
));
}
return builder.build();
}
}

View File

@@ -1,53 +0,0 @@
package com.example.fitbot.exercise;
/**
* The ExerciseUser class represents a user of the exercise application.
* This contains all necessary information of the current user.
*/
public class ExerciseUser {
public float upperArmLength;
public float lowerArmLength;
public float upperLegLength;
public float lowerLegLength;
public float height;
/**
* Constructor for the ExerciseUser class.
* @param upperArmLength The length of the upper arm.
* @param lowerArmLength The length of the lower arm.
* @param height The height of the user.
* @param upperLegLength The length of the upper leg.
* @param lowerLegLength The length of the lower leg.
*/
public ExerciseUser(float upperArmLength, float lowerArmLength, float height, float upperLegLength, float lowerLegLength) {
this.upperArmLength = upperArmLength;
this.lowerArmLength = lowerArmLength;
this.upperLegLength = upperLegLength;
this.lowerLegLength = lowerLegLength;
this.height = height;
}
/**
* Constructor for the ExerciseUser class.
* @param height The height of the user.
*/
public ExerciseUser(float height) {
this.height = height;
this.upperArmLength = height * 0.2f;
this.lowerArmLength = height * 0.2f;
this.upperLegLength = height * 0.3f;
this.lowerLegLength = height * 0.3f;
}
/**
* Default constructor for the ExerciseUser class.
* This sets the default height to 180.0f. (1.80m)
*/
public ExerciseUser() {
this(180.0f);
}
}

View File

@@ -0,0 +1,6 @@
package com.example.fitbot.pepper;
public abstract class AbstractPepperActionEvent {
public abstract EPepperAction getAction();
}

View File

@@ -0,0 +1,10 @@
package com.example.fitbot.pepper;
/**
* Enum for Pepper actions
*/
public enum EPepperAction {
ACTION_SPEAK,
ACTION_ANIMATE,
NO_ACTION
}

View File

@@ -0,0 +1,144 @@
package com.example.fitbot.pepper;
import com.aldebaran.qi.sdk.QiContext;
import com.aldebaran.qi.sdk.RobotLifecycleCallbacks;
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 java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* Class representing the Pepper robot.
* All functionality related to Pepper is implemented in this class.
* <p>
* Before any of the functions in this class can be used, the Pepper class needs a QiContext to be
* set. This is retrieved in the `onRobotFocusGained` method of the RobotLifecycleCallbacks
* interface, and can then provided using the `provideContext` method.
* </p>
*/
public class Pepper {
// Queue containing all Pepper actions that need to be executed
// This is to prevent the app from crashing when attempting to perform multiple actions at once
private static final ConcurrentLinkedQueue<AbstractPepperActionEvent> pepperActionEventQueue =
new ConcurrentLinkedQueue<>();
private static final AtomicBoolean isAnimating = new AtomicBoolean(false);
private static final AtomicBoolean isSpeaking = new AtomicBoolean(false);
private static final Locale DEFAULT_LOCALE = new Locale(Language.DUTCH, Region.NETHERLANDS);
/**
* The latest QiContext used by Pepper.
* This can be publicly accessed to get the latest QiContext used by Pepper.
*/
public static QiContext latestContext = null;
/**
* Make the Pepper robot speak a phrase.
* This function adds a SpeechEvent to the event queue,
* and will speak the phrase once Pepper is ready to process it.
*
* @param phrase The phrase to speak
*/
public static void say(String phrase) {
addToEventQueue(new PepperSpeechEvent(phrase, DEFAULT_LOCALE));
}
/**
* Make the Pepper robot perform an animation.
* This function adds an AnimationEvent to the event queue,
* and will perform the animation once Pepper is ready to process it.
*
* @param animationName The name of the animation to play
*/
public static void animate(String animationName) {
addToEventQueue(new PepperAnimationEvent(animationName));
}
/**
* Add an event to the event queue.
* The event will be executed once Pepper is ready to process it.
*
* @param event The event to add to the queue
*/
public static void addToEventQueue(AbstractPepperActionEvent event) {
pepperActionEventQueue.add(event);
processEventQueue();
}
/**
* Process the event queue.
* This method will process the next event in the queue if Pepper is not currently
* speaking or animating.
* This prevents multiple actions from being executed at the same time, which can
* cause the application to crash.
*/
private static synchronized void processEventQueue() {
if (!pepperActionEventQueue.isEmpty()) {
AbstractPepperActionEvent event = pepperActionEventQueue.poll();
// Prevent null pointer exceptions
if (event == null || latestContext == null)
return;
switch (event.getAction()) {
/* Event for speaking a phrase **/
case ACTION_SPEAK:
if (!(event instanceof PepperSpeechEvent) || isSpeaking.get())
break;
PepperSpeechEvent speechEvent = (PepperSpeechEvent) event;
isSpeaking.set(true);
speechEvent
.getSay(latestContext)
.async()
.run()
.andThenConsume(future -> {
isSpeaking.set(false);
processEventQueue();
});
break;
/* Event for animating the robot **/
case ACTION_ANIMATE:
if (!(event instanceof PepperAnimationEvent) || isAnimating.get())
break;
PepperAnimationEvent animationEvent = (PepperAnimationEvent) event;
animationEvent
.getAnimation(latestContext)
.async()
.run()
.andThenConsume(future -> {
isAnimating.set(false);
processEventQueue();
});
break;
default:
// Do nothing
break;
}
}
}
/**
* Function for providing the latest QiContext to Pepper.
*
* @param context The QiContext to use.
* This can be null if the context is not available.
* @param origin The origin of the context.
* This parameter is to prevent other classes from
* unnecessarily providing a context, or setting
* the context to null.
*/
public static void provideContext(QiContext context, Class<? extends RobotLifecycleCallbacks> origin) {
latestContext = context;
if (context != null) {
processEventQueue();
}
}
}

View File

@@ -0,0 +1,65 @@
package com.example.fitbot.pepper;
import com.aldebaran.qi.sdk.QiContext;
import com.aldebaran.qi.sdk.builder.AnimateBuilder;
import com.aldebaran.qi.sdk.builder.AnimationBuilder;
import com.aldebaran.qi.sdk.object.actuation.Animate;
import com.aldebaran.qi.sdk.object.actuation.Animation;
public class PepperAnimationEvent extends AbstractPepperActionEvent {
public final String animationName;
private final IAnimationCompletedListener onLabelReachedListener;
/**
* Constructor for PepperAnimationEvent
*
* @param animationName The name of the animation to play
* @param onLabelReachedListener The listener to call when the animation is completed
*/
public PepperAnimationEvent(String animationName, IAnimationCompletedListener onLabelReachedListener) {
this.animationName = animationName;
this.onLabelReachedListener = onLabelReachedListener;
}
/**
* Constructor for PepperAnimationEvent
*
* @param animationName The name of the animation to play
*/
public PepperAnimationEvent(String animationName) {
this(animationName, null);
}
@Override
public EPepperAction getAction() {
return null;
}
/**
* Returns an animation object, which can be used to play the animation
* in the Pepper class.
*/
public Animate getAnimation(QiContext context) {
Animation animation = AnimationBuilder.with(context)
.withResources(context.getResources().getIdentifier(animationName, "raw", context.getPackageName()))
.build();
Animate animate = AnimateBuilder.with(context)
.withAnimation(animation)
.build();
// Add a listener for when a label is reached
animate.addOnLabelReachedListener((label, time) -> {
if (onLabelReachedListener != null && "end".equals(label)) {
onLabelReachedListener.onComplete();
}
});
return animate;
}
public interface IAnimationCompletedListener {
void onComplete();
}
}

View File

@@ -0,0 +1,32 @@
package com.example.fitbot.pepper;
import com.aldebaran.qi.sdk.QiContext;
import com.aldebaran.qi.sdk.builder.SayBuilder;
import com.aldebaran.qi.sdk.object.conversation.Say;
import com.aldebaran.qi.sdk.object.locale.Locale;
public class PepperSpeechEvent extends AbstractPepperActionEvent {
public final String phrase;
public final Locale locale;
public PepperSpeechEvent(String phrase, Locale locale) {
this.locale = locale;
this.phrase = phrase;
}
@Override
public EPepperAction getAction() {
return EPepperAction.ACTION_SPEAK;
}
/**
* Returns a Say object, which can then be executed.
*/
public Say getSay(QiContext context) {
return SayBuilder.with(context)
.withText(this.phrase)
.withLocale(this.locale)
.build();
}
}

View File

@@ -4,8 +4,7 @@ import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import com.example.fitbot.R;
import com.example.fitbot.ui.activities.FitnessActivity;
import com.example.fitbot.ui.activities.MainActivity;
import com.example.fitbot.util.NavigationManager;
public class EndScreenActivity extends AppCompatActivity {
@@ -14,7 +13,7 @@ public class EndScreenActivity extends AppCompatActivity {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_end_screen);
com.example.fitbot.util.ButtonNavigation.setupButtonNavigation(this, R.id.homeButton, MainActivity.class);
com.example.fitbot.util.ButtonNavigation.setupButtonNavigation(this, R.id.continueButton, FitnessActivity.class);
NavigationManager.setupButtonNavigation(this, R.id.homeButtonEndScreen, MainActivity.class);
NavigationManager.setupButtonNavigation(this, R.id.startButtonEndScreen, FitnessActivity.class);
}
}

View File

@@ -1,7 +1,14 @@
package com.example.fitbot.ui.activities;
import android.app.Dialog;
import android.content.Context;
import android.graphics.drawable.ColorDrawable;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.VideoView;
import com.aldebaran.qi.sdk.QiContext;
@@ -10,25 +17,41 @@ 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.exercise.EMuscleGroup;
import com.example.fitbot.exercise.Exercise;
import com.example.fitbot.ui.components.PersonalMotionPreviewElement;
import com.example.fitbot.util.ButtonNavigation;
import com.example.fitbot.util.FitnessCycle;
import com.example.fitbot.util.path.GesturePath;
import com.example.fitbot.exercise.ExerciseManager;
import com.example.fitbot.pepper.Pepper;
import com.example.fitbot.ui.components.ExerciseStatusElement;
import com.example.fitbot.util.NavigationManager;
import com.example.fitbot.util.processing.InputProcessor;
import org.joml.Vector3f;
import java.util.concurrent.CompletableFuture;
public class FitnessActivity extends RobotActivity implements RobotLifecycleCallbacks {
PersonalMotionPreviewElement personalMotionPreviewElement;
// Private fields for the FitnessActivity class.
private ExerciseStatusElement personalMotionPreviewElement;
private InputProcessor motionProcessor;
private Exercise currentExercise;
// Some nice little messages for the user
private static final String[] EXERCISE_NOT_FOUND_MESSAGES = new String[]{
"Ik heb momenteel helaas wat moeite met het ophalen van oefeningen, sorry.",
"Het lijkt erop dat de oefeningen op een misterieus avontuur zijn. Even wachten tot ze terug zijn.",
", de oefeningen slapen nog, probeer het later nog eens."
};
private static final String EXERCISE_NOT_FOUND_SEEK_HELP_MESSAGE =
"Indien dit probleem zich voortzet, neem contact op met de ontwikkelaar.";
private static final float SENSOR_SAMPLE_RATE = 10.0f;
private static final int EXERCISE_COUNT = 5;
private static final float EXERCISE_SPEED_MULTIPLIER = 1.0f;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
QiSDK.register(this, this);
setContentView(R.layout.activity_fitness);
// Remove the ugly ass bar on top of the view
@@ -36,62 +59,137 @@ public class FitnessActivity extends RobotActivity implements RobotLifecycleCall
// Find the VideoView by its ID
VideoView videoView = findViewById(R.id.videoView);
playVideo(videoView, this);
NavigationManager.setupButtonNavigation(this, R.id.homeButtonFitness, MainActivity.class);
NavigationManager.setupButtonNavigation(this, R.id.skipButtonFitness, MainActivity.class); //Needs to skip exercises once those are implemented
FitnessCycle.playVideo(videoView, this);
NavigationManager.hideSystemUI(this);
ButtonNavigation.setupButtonNavigation(this, R.id.homeButton, MainActivity.class);
ButtonNavigation.setupButtonNavigation(this, R.id.buttonComplete, EndScreenActivity.class);
// Implement your logic when the robot focus is gained
GesturePath.Builder gesturePathBuilder = new GesturePath.Builder();
/* Generate a random path to test the tracking system */
for ( int i = 0; i < 40; i++)
{
gesturePathBuilder.addVector(
new Vector3f(
(float)Math.cos(Math.PI + (Math.PI / 40.0f) * i),
(float)Math.sin(Math.PI + (Math.PI / 40.0f) * i),
0
)
);
Button infoButtonFitness = findViewById(R.id.infoButtonFitness);
infoButtonFitness.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
showInfoDialog();
}
personalMotionPreviewElement = findViewById(R.id.personalMotionPreviewElement);
personalMotionPreviewElement.post(() -> {
Log.i("FitnessActivity", "PersonalMotionPreviewElement.post()");
Exercise exercise = new Exercise(EMuscleGroup.ARMS, "Bicep Curls", "Oefening voor de biceps.", gesturePathBuilder.build(), gesturePathBuilder.build());
personalMotionPreviewElement.initialize(exercise);
});
}
@Override
public void onRobotFocusGained(QiContext qiContext) {
// Find the VideoView by its ID
// CompletableFuture.runAsync(() -> FitnessCycle.executeMovement("bicepcurl", 10, qiContext));
Log.i("Motion", "qiContext provided");
personalMotionPreviewElement.provideQiContext(qiContext);
// Provide the context so that all queued actions can be performed.
Pepper.provideContext(qiContext, this.getClass());
personalMotionPreviewElement = findViewById(R.id.personalMotionPreviewElement);
// Initialize the element whenever it has been added to the screen.
// This will provide the element with the appropriate dimensions for drawing
// the canvas properly.
personalMotionPreviewElement.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()};
motionProcessor = new InputProcessor(vectors, exercise.exerciseTimeInSeconds, SENSOR_SAMPLE_RATE);
personalMotionPreviewElement.initialize(exercise, motionProcessor, EXERCISE_COUNT);
motionProcessor.startListening();
motionProcessor.setInputHandler(personalMotionPreviewElement);
}, (n) -> {
int randomMessageIndex = (int) Math.floor(Math.random() * EXERCISE_NOT_FOUND_MESSAGES.length);
Pepper.say(EXERCISE_NOT_FOUND_MESSAGES[randomMessageIndex]);
Pepper.say(EXERCISE_NOT_FOUND_SEEK_HELP_MESSAGE);
NavigationManager.navigateToActivity(this, EndScreenActivity.class);
});
});
// FitnessCycle.playVideo(qiContext, videoView, this);
}
/**
* Acquire an exercise from the ExerciseManager.
* Whenever the retrieval failed, it will have the robot say something to the user
* to inform them about the issue.
*/
public void fetchExerciseAsync(Exercise.ExerciseFetchHandler onSuccessfulFetch, Exercise.ExerciseFetchHandler onFailedFetch) {
// For some stupid reason we cannot perform network operations on the main thread.
// therefore we'll have to do it like this...
(new Thread(() -> {
Exercise exercise = ExerciseManager.fetchExerciseFromDatabase();
if (exercise == null) {
onFailedFetch.handle(null);
} else {
onSuccessfulFetch.handle(exercise);
}
})).start();
}
/**
* 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) {
Uri videoUri = Uri.parse("android.resource://" + context.getPackageName() + "/" + R.raw.bicepvideo);
videoView.setVideoURI(videoUri);
videoView.setOnCompletionListener(mp -> {
// Repeat the video when it finishes playing
videoView.start();
});
videoView.start();
} else {
Log.e("FitnessActivity", "VideoView is null. Check your layout XML.");
}
}
@Override
public void onRobotFocusLost() {
// Implement your logic when the robot focus is lost
QiSDK.unregister(this, this);
Pepper.provideContext(null, this.getClass()); // Remove the context (unavailable)
// Go to the main screen
NavigationManager.navigateToActivity(this, MainActivity.class);
}
@Override
public void onRobotFocusRefused(String reason) {
// Implement your logic when the robot focus is refused
}
@Override
protected void onDestroy() {
super.onDestroy();
if (this.motionProcessor != null) {
this.motionProcessor.stopListening();
this.motionProcessor = null;
}
QiSDK.unregister(this, this);
this.personalMotionPreviewElement.onDestroy();
Pepper.provideContext(null, this.getClass());
super.onDestroy();
}
private void showInfoDialog() {
final Dialog dialog = new Dialog(this);
dialog.setContentView(R.layout.dialog_info);
NavigationManager.hideSystemUI(this);
dialog.getWindow().setDimAmount(0.5f);
dialog.getWindow().setBackgroundDrawable(new ColorDrawable(android.graphics.Color.TRANSPARENT));
dialog.setCancelable(true);
Button closeButton = dialog.findViewById(R.id.closeButtonDialog);
closeButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
dialog.dismiss();
}
});
dialog.show();
}
}

View File

@@ -1,10 +1,10 @@
package com.example.fitbot.ui.activities;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import com.example.fitbot.R;
import com.example.fitbot.util.ButtonNavigation;
import com.example.fitbot.util.NavigationManager;
public class HelpActivity extends AppCompatActivity {
@@ -13,7 +13,8 @@ public class HelpActivity extends AppCompatActivity {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_help);
ButtonNavigation.setupButtonNavigation(this, R.id.homeButton, MainActivity.class);
NavigationManager.setupButtonNavigation(this, R.id.homeButtonHelp, MainActivity.class);
NavigationManager.hideSystemUI(this);
}
}

View File

@@ -1,5 +1,6 @@
package com.example.fitbot.ui.activities;
import static com.example.fitbot.util.Networking.sendIpAddress;
import android.annotation.SuppressLint;
import android.content.Intent;
import android.net.Uri;
@@ -10,11 +11,16 @@ import android.support.v4.widget.DrawerLayout;
import android.support.v7.app.ActionBarDrawerToggle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.View;
import android.view.WindowManager;
import android.widget.Button;
import com.example.fitbot.R;
import com.example.fitbot.util.ButtonNavigation;
import com.example.fitbot.util.NavigationManager;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
public class MainActivity extends AppCompatActivity {
@@ -25,13 +31,16 @@ public class MainActivity extends AppCompatActivity {
Button startButton;
@SuppressLint("WrongViewCast")
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Set full screen mode to hide status bar
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
Button startButton = findViewById(R.id.startButton);
startButton = findViewById(R.id.startButtonMain);
startButton.setOnClickListener(v -> {
Uri videoUri = Uri.parse("android.resource://" + getPackageName() + "/" + R.raw.bicepvideo);
Intent intent = new Intent(MainActivity.this, FitnessActivity.class);
@@ -39,7 +48,12 @@ public class MainActivity extends AppCompatActivity {
startActivity(intent);
});
setUpUi(); // Set up the UI
// Set up the UI
setUpUi();
// Hide system UI
NavigationManager.hideSystemUI(this);
sendIpAddress(this);
}
private void setUpUi() {
@@ -47,31 +61,58 @@ public class MainActivity extends AppCompatActivity {
drawerLayout = findViewById(R.id.drawer_layout);
navigationView = findViewById(R.id.nav_view);
toolbar = findViewById(R.id.toolbar);
startButton = findViewById(R.id.startButton);
startButton = findViewById(R.id.startButtonMain);
ButtonNavigation.setupButtonNavigation(this, R.id.startButton, FitnessActivity.class);
ButtonNavigation.setupButtonNavigation(this, R.id.helpButton, HelpActivity.class);
// Hide the action bar
if (getSupportActionBar() != null) {
getSupportActionBar().hide();
}
NavigationManager.setupButtonNavigation(this, R.id.startButtonMain, FitnessActivity.class);
NavigationManager.setupButtonNavigation(this, R.id.helpButtonMain, HelpActivity.class);
/*---Tool Bar---*/
setSupportActionBar(toolbar); // Make the toolbar act as the action bar
if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayShowTitleEnabled(false); // Remove the title from the toolbar
}
/*---Navigation Drawer Menu---*/
navigationView.bringToFront(); // Make the navigation drawer menu clickable
ActionBarDrawerToggle toggle = new // Create a toggle for the navigation drawer
ActionBarDrawerToggle(this,drawerLayout,toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close);
ActionBarDrawerToggle(this, drawerLayout, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close) {
@Override
public void onDrawerOpened(View drawerView) {
super.onDrawerOpened(drawerView);
}
@Override
public void onDrawerClosed(View drawerView) {
super.onDrawerClosed(drawerView);
}
};
drawerLayout.addDrawerListener(toggle);
toggle.syncState(); // Synchronize the state of the navigation drawer
}
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if (hasFocus) {
NavigationManager.hideSystemUI(this);
}
}
@Override
public void onBackPressed() { // Close the navigation drawer when the back button is pressed
if (drawerLayout.isDrawerOpen(GravityCompat.START)) {
drawerLayout.closeDrawer(GravityCompat.START);
}
else
{super.onBackPressed();
} else {
super.onBackPressed();
}
}
}

View File

@@ -0,0 +1,227 @@
package com.example.fitbot.ui.components;
import android.app.Activity;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import com.example.fitbot.exercise.Exercise;
import com.example.fitbot.pepper.Pepper;
import com.example.fitbot.ui.activities.EndScreenActivity;
import com.example.fitbot.ui.activities.FitnessActivity;
import com.example.fitbot.ui.activities.MainActivity;
import com.example.fitbot.util.NavigationManager;
import com.example.fitbot.util.processing.IInputHandler;
import com.example.fitbot.util.processing.InputProcessor;
import org.joml.Matrix4f;
import org.joml.Vector2f;
import org.joml.Vector3f;
import org.joml.Vector4f;
import java.util.concurrent.ConcurrentLinkedQueue;
public class ExerciseStatusElement extends View implements IInputHandler {
// Fields regarding Exercise and speech handling.
private InputProcessor motionProcessor;
private Exercise exercise;
private int exerciseCount;
private FitnessActivity parentActivity;
private final Paint userProgressPaint = new Paint();
private final Paint borderPaint = new Paint();
private final Paint backgroundPaint = new Paint();
// TODO: Remove
private final Matrix4f viewMatrix = new Matrix4f(); // The view matrix for the 3D to 2D transformation.
private final Matrix4f projectionMatrix = new Matrix4f(); // The projection matrix for the 3D to 2D transformation.
private final Vector4f objectPosition = new Vector4f(0, 0, 0, 1); // The location of the object in 3D space.
private final ConcurrentLinkedQueue<Vector2f> vectors = new ConcurrentLinkedQueue<>();
// TODO: Remove
private Vector2f[] axisVectors = new Vector2f[0];
private static final String[] STARTING_PHRASES = {
"Veel success met de oefening!",
"Je kan het!",
"Veel plezier!"
};
public ExerciseStatusElement(Context context, AttributeSet attrs) {
super(context, attrs);
if (context instanceof Activity) {
this.parentActivity = (FitnessActivity) context;
}
this.userProgressPaint.setColor(0xFFFF0000); // Red
this.userProgressPaint.setStyle(Paint.Style.FILL);
this.userProgressPaint.setStrokeWidth(5.0f);
this.userProgressPaint.setAntiAlias(true);
// Target paint is the filling of the target path.
this.borderPaint.setColor(-1);
this.borderPaint.setStyle(Paint.Style.STROKE);
this.borderPaint.setStrokeWidth(5.0f);
this.borderPaint.setAntiAlias(true);
this.backgroundPaint.setColor(0xFF000000); // Black
}
/**
* Method for initializing the PersonalMotionPreviewElement.
* This method has to be called with a "post" function when the element has been
* created, otherwise the dimensions of the element aren't initialized yet, which
* will cause the vertex projections to fail (0 width and height).
*
* @param exercise The exercise that the user is currently performing.
* @param motionProcessor The motion processor that will be used to process the user's motion.
* @param exerciseCount The total amount of exercises that the user has to perform.
*/
public void initialize(@Nullable Exercise exercise, InputProcessor motionProcessor, int exerciseCount) {
Log.i("PersonalMotionPreviewElement", "Creating new PersonalMotionPreviewElement.");
this.motionProcessor = motionProcessor;
this.exercise = exercise;
this.exerciseCount = exerciseCount;
/* TODO: Remove */
this.axisVectors = new Vector2f[]{
projectVertex(new Vector3f(-5.0f, 0, 0), getWidth(), getHeight()),
projectVertex(new Vector3f(5.0f, 0, 0), getWidth(), getHeight()),
projectVertex(new Vector3f(0, -5.0f, 0), getWidth(), getHeight()),
projectVertex(new Vector3f(0, 5.0f, 0), getWidth(), getHeight()),
projectVertex(new Vector3f(0, 0, -5.0f), getWidth(), getHeight()),
projectVertex(new Vector3f(0, 0, 5.0f), getWidth(), getHeight())
};
Pepper.say(STARTING_PHRASES[(int) Math.floor(Math.random() * STARTING_PHRASES.length)]);
// Handler that is called every time the motion processor receives new data.
this.motionProcessor.setInputHandler((rotationVector, deviceId) -> {
Log.i("MotionProcessor", "Rotation vector received: " + rotationVector);
Log.i("MotionProcessor", "Last error offset:" + this.motionProcessor.getError(deviceId, this.motionProcessor.secondsPassed()));
if (this.motionProcessor.hasFinished()) {
// If for some reason the parent activity is not defined,
// move back to the main screen.
if (this.parentActivity == null) {
// Move to main screen
NavigationManager.navigateToActivity(getContext(), MainActivity.class);
return;
}
// Move on to the next exercise, or finish.
if (this.exerciseCount > 0) {
this.exerciseCount--;
this.parentActivity.fetchExerciseAsync((newExercise) -> {
this.motionProcessor.useExercise(newExercise);
}, (failed) -> {
// Move to main screen
NavigationManager.navigateToActivity(parentActivity, MainActivity.class);
});
} else {
// Finish the exercise.
NavigationManager.navigateToActivity(parentActivity, EndScreenActivity.class);
return;
}
}
/* TODO: Use raw vector */
vectors.add(projectVertex(rotationVector, this.getWidth(), this.getHeight()));
/* TODO: Remove */
Vector2f parsed = projectVertex(rotationVector, this.getWidth(), this.getHeight());
this.vectors.add(parsed /* TODO: Add regular vertex once exercise data is stored in DB*/);
// Remove the first element if the array is too big
if (this.vectors.size() > 100)
this.vectors.poll();
});
}
/**
* Method for setting the gesture path that will be drawn on the canvas.
*
* @param exercise The exercise that the user is currently performing.
*/
public void setExercise(Exercise exercise) {
this.motionProcessor.useExercise(exercise);
this.exercise = exercise;
}
private Vector2f projectVertex(Vector3f point, int virtualWidth, int virtualHeight) {
viewMatrix
.identity()
.lookAt(new Vector3f(0, 0, -2), new Vector3f(0, 0, 0), new Vector3f(0, 1, 0));
// Transform the projection matrix to a perspective projection matrix
// Perspective transformation conserves the depth of the object
projectionMatrix
.identity()
.perspective((float) Math.toRadians(70), (float) virtualWidth / virtualHeight, .01f, 1000.0f);
// Convert world coordinates to screen-space using MVP matrix
Vector4f screenCoordinates = new Vector4f(point, 1.0f)
.mul(this.viewMatrix)
.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;
return new Vector2f(normalizedX, normalizedY);
}
@Override
public void onDraw(Canvas canvas) {
canvas.drawRect(0, 0, getWidth(), getHeight(), backgroundPaint);
this.setBackgroundColor(0xFF000000); // Black
/*if (this.exercise == null)
return;*/
/* TODO: Remove */
for (int i = 0, startX, endX, startY, endY; i < axisVectors.length / 2; i++) {
startX = (int) Math.max(0, Math.min(this.getWidth(), (int) axisVectors[i * 2].x));
endX = (int) Math.max(0, Math.min(this.getWidth(), (int) axisVectors[i * 2 + 1].x));
startY = (int) Math.max(0, Math.min(this.getHeight(), (int) axisVectors[i * 2].y));
endY = (int) Math.max(0, Math.min(this.getHeight(), (int) axisVectors[i * 2 + 1].y));
canvas.drawLine(startX, startY, endX, endY, this.borderPaint);
}
for (Vector2f point : this.vectors) {
canvas.drawRect(point.x, point.y, point.x + 5, point.y + 5, this.userProgressPaint);
}
/*
// 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, (targetRadius * exerciseProgress.get()/1000.0f), this.referencePaint);
referencePaint.setColor(
Color.argb(
255,
(int)(255 * (1.0 - exerciseProgress.get()/1000.0f)),
(int)(255 * exerciseProgress.get()/1000.0f),
0
)
);*/
this.invalidate();
}
@Override
public void accept(Vector3f rotationVector, int sensorId) {
}
}

View File

@@ -1,178 +0,0 @@
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;
import android.util.Log;
import android.view.View;
import com.aldebaran.qi.sdk.QiContext;
import com.example.fitbot.exercise.Exercise;
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;
import com.example.fitbot.util.processing.MotionProcessor;
import org.joml.Matrix4f;
import org.joml.Vector2f;
import org.joml.Vector3f;
import org.joml.Vector4f;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
public class PersonalMotionPreviewElement extends View {
private GesturePath[] paths;
private MotionProcessor motionProcessor;
private double pathTime = 0.0D; // The timestamp at which the path is currently at.
private final AtomicInteger exerciseProgress = new AtomicInteger(0); // The progress of the exercise. Ranges from 0 to 1000.
private QiContext qiContext;
private Exercise exercise;
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 static final String[] USER_PHRASES = {
"Veel success met de oefening!",
"Je kan het!",
"Veel plezier!"
};
private double timePassed = 0.0D; // The time that has passed since the start of the exercise, in seconds.
private long startingTime = 0L;
private Vector2f screenDimensions = new Vector2f(); // Width and height dimensions of the screen
public PersonalMotionPreviewElement(Context context, AttributeSet attrs) {
super(context, attrs);
this.referencePaint.setColor(0xFFFF0000); // Red
this.referencePaint.setStyle(Paint.Style.FILL);
this.referencePaint.setStrokeWidth(5.0f);
this.referencePaint.setAntiAlias(true);
// 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);
}
/**
* Method for initializing the PersonalMotionPreviewElement.
* This method has to be called with a "post" function when the element has been
* created, otherwise the dimensions of the element aren't initialized yet, which
* will cause the vertex projections to fail (0 width and height).
*
* @param exercise The exercise that the user is currently performing.
*/
public void initialize(Exercise exercise) {
Log.i("PersonalMotionPreviewElement", "Creating new PersonalMotionPreviewElement.");
this.backgroundColor.setColor(0xFF000000); // Black
this.screenDimensions.x = this.getWidth();
this.screenDimensions.y = this.getHeight();
this.actualPath = new Path();
this.targetPath = new Path();
this.startingTime = System.nanoTime(); // Set the last time to the current time
this.exercise = exercise;
this.paths = exercise.getPath();
}
public void onDestroy()
{
if ( this.motionProcessor != null )
this.motionProcessor.stopListening();
this.motionProcessor = null;
}
/**
* 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) {
try {
this.qiContext = context;
if (this.motionProcessor != null)
this.motionProcessor.stopListening();
this.motionProcessor = new MotionProcessor();
this.motionProcessor.startListening();
// Handler that is called every time the motion processor receives new data.
this.motionProcessor.setMotionDataEventHandler((processed, preprocessed, sampleIndex, sampleRate, deviceId) -> {
int progress = (int) this.motionProcessor.getError(this.paths[0], processed);
this.exerciseProgress.set(Math.min(1000, Math.max(0, progress)));
Log.i("MotionProcessor", "Processed data: " + progress + " (" + preprocessed + ")");
});
saySomethingNice();
} catch (Exception e) {
Log.e("MotionProcessor", "An error occurred whilst attempting to provide QiContext:" + e.getMessage());
}
}
/**
* Function to say something nice to the user :)
*/
private void saySomethingNice()
{
if (this.qiContext == null)
return;
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 exercise The exercise that the user is currently performing.
*/
public void setExercise(Exercise exercise) {
this.exercise = exercise;
}
@Override
public void onDraw(Canvas canvas) {
canvas.drawRect(0, 0, getWidth(), getHeight(), backgroundColor);
this.setBackgroundColor(0xFF000000); // Black
if (this.exercise == null)
return;
// 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, (targetRadius * exerciseProgress.get()/1000.0f), this.referencePaint);
referencePaint.setColor(
Color.argb(
255,
(int)(255 * (1.0 - exerciseProgress.get()/1000.0f)),
(int)(255 * exerciseProgress.get()/1000.0f),
0
)
);
this.invalidate();
timePassed = (System.nanoTime() - startingTime) / 1E9D;
}
}

View File

@@ -11,8 +11,7 @@ import com.aldebaran.qi.sdk.object.actuation.Animation;
public class Animations {
public static void Animate(String AnimationFile, QiContext ctx)
{
public static void Animate(String AnimationFile, QiContext ctx) {
int resId = ctx.getResources().getIdentifier(AnimationFile, "raw", ctx.getPackageName());
Animation animation = AnimationBuilder.with(ctx)

View File

@@ -1,26 +0,0 @@
package com.example.fitbot.util;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.view.View;
import android.widget.Button;
public class ButtonNavigation {
/**
* Sets up a button to navigate to a different activity when clicked.
*
* @param currentActivity The activity that contains the button
* @param buttonId The ID of the button
* @param targetActivity The activity to navigate to
*/
public static void setupButtonNavigation(Activity currentActivity, int buttonId, Class<? extends Activity> targetActivity) {
Button button = currentActivity.findViewById(buttonId);
button.setOnClickListener(v -> {
Intent intent = new Intent(currentActivity, targetActivity);
currentActivity.startActivity(intent);
currentActivity.finish();
});
}
}

View File

@@ -1,108 +0,0 @@
package com.example.fitbot.util;
import android.content.Context;
import android.net.Uri;
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;
import com.aldebaran.qi.sdk.builder.AnimationBuilder;
import com.aldebaran.qi.sdk.object.actuation.Animate;
import com.aldebaran.qi.sdk.object.actuation.Animation;
import java.util.concurrent.atomic.AtomicInteger;
public class FitnessCycle extends AppCompatActivity {
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)
.withResources(qiContext.getResources().getIdentifier(Exercise, "raw", qiContext.getPackageName()))
.build();
Animate animate = AnimateBuilder.with(qiContext)
.withAnimation(animation)
.build();
// Add a listener for when a label is reached
animate.addOnLabelReachedListener((label, time) -> {
// Increment repCount when the end of a repetition is reached
if ("end".equals(label)) {
repCount.incrementAndGet();
}
});
// Run the animation the desired number of times
for (int i = 0; i < Reps; i++) {
animate.run();
}
}
/**
* 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) {
Uri videoUri = Uri.parse("android.resource://" + context.getPackageName() + "/" + R.raw.bicepvideo);
videoView.setVideoURI(videoUri);
videoView.setOnCompletionListener(mp -> {
// Repeat the video when it finishes playing
videoView.start();
});
videoView.start();
} else {
Log.e("FitnessActivity", "VideoView is null. Check your layout XML.");
}
}
}

View File

@@ -0,0 +1,60 @@
package com.example.fitbot.util;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.view.View;
import android.widget.Button;
public class NavigationManager {
/**
* Sets up a button to navigate to a different activity when clicked.
*
* @param currentActivity The activity that contains the button
* @param buttonId The ID of the button
* @param targetActivity The activity to navigate to
*/
public static void setupButtonNavigation(Activity currentActivity, int buttonId, Class<? extends Activity> targetActivity) {
Button button = currentActivity.findViewById(buttonId);
if (button == null) {
throw new IllegalArgumentException("Button with ID " + buttonId + " not found in " + currentActivity.getClass().getSimpleName());
}
button.setOnClickListener(v -> NavigationManager.navigateToActivity(currentActivity, targetActivity));
}
/**
* Navigates to the target activity.
*
* @param currentActivity The current activity
* @param targetActivity The target activity
*/
public static void navigateToActivity(Activity currentActivity, Class<? extends Activity> targetActivity) {
Intent intent = new Intent(currentActivity, targetActivity);
// Close previous activity
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
currentActivity.startActivity(intent);
currentActivity.finish();
}
/**
* Navigates to the target activity.
*
* @param context The context
* @param targetActivity The target activity
*/
public static void navigateToActivity(Context context, Class<? extends Activity> targetActivity) {
Intent intent = new Intent(context, targetActivity);
context.startActivity(intent);
}
public static void hideSystemUI(Activity currentActivity) {
View decorView = currentActivity.getWindow().getDecorView();
// Hide the status bar and navigation bar
int uiOptions = View.SYSTEM_UI_FLAG_FULLSCREEN
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
decorView.setSystemUiVisibility(uiOptions);
}
}

View File

@@ -0,0 +1,76 @@
package com.example.fitbot.util;
import android.content.Context;
import android.net.wifi.WifiManager;
import android.os.AsyncTask;
import android.util.Log;
import java.io.OutputStream;
import java.math.BigInteger;
import java.net.HttpURLConnection;
import java.net.InetAddress;
import java.net.URL;
import java.net.UnknownHostException;
import java.nio.ByteOrder;
import java.nio.charset.StandardCharsets;
public class Networking {
public static void sendIpAddress(final Context context) {
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... params) {
String ipAddress = getIPAddress(context);
String jsonInputString = "{\"ip\":\"" +
ipAddress +
"\"}";
byte[] input = jsonInputString.getBytes(StandardCharsets.UTF_8);
HttpURLConnection conn = null;
try {
URL url = new URL("http://145.92.8.132:443/set-ip"); // Replace with your Node server URL
conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("POST");
conn.setRequestProperty("Content-Type", "application/json");
conn.setDoOutput(true);
OutputStream os = conn.getOutputStream();
os.write(input, 0, input.length);
os.close();
int responseCode = conn.getResponseCode();
Log.i("NetworkUtils", "Response Code: " + responseCode);
} catch (Exception e) {
Log.e("NetworkUtils", "Error sending IP address", e);
} finally {
if (conn != null) {
conn.disconnect();
}
}
return null;
}
}.execute();
}
private static String getIPAddress(Context context) {
WifiManager wifiManager = (WifiManager) context.getApplicationContext().getSystemService(Context.WIFI_SERVICE);
int ipAddress = wifiManager.getConnectionInfo().getIpAddress();
// Convert little-endian to big-endian if needed
if (ByteOrder.nativeOrder().equals(ByteOrder.LITTLE_ENDIAN)) {
ipAddress = Integer.reverseBytes(ipAddress);
}
byte[] ipByteArray = BigInteger.valueOf(ipAddress).toByteArray();
String ip = "";
try {
ip = InetAddress.getByAddress(ipByteArray).getHostAddress();
} catch (UnknownHostException ex) {
Log.e("WIFIIP", "Unable to get host address.");
}
return ip;
}
}

View File

@@ -2,10 +2,6 @@ package com.example.fitbot.util.path;
import org.joml.Vector3f;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class GesturePath {
// The vectors that make up the path.
@@ -16,8 +12,7 @@ public class GesturePath {
*
* @param vectors The vectors that make up the path.
*/
public GesturePath(Vector3f[] vectors)
{
public GesturePath(Vector3f[] vectors) {
if (vectors.length < 2)
throw new IllegalArgumentException("A path must have at least two points.");
@@ -28,6 +23,7 @@ public class GesturePath {
/**
* Constructor for a GesturePath with provided PathSegments.
*
* @param segments The PathSegments to initialize the path with.
*/
public GesturePath(PathSegment... segments) {
@@ -43,6 +39,18 @@ public class GesturePath {
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.
*
@@ -71,48 +79,40 @@ public class GesturePath {
return closest(referencePoint).difference(referencePoint); // Get the closest segment and calculate the error.
}
// Builder class for the GesturePath object.
public static class Builder {
// List of vectors to add to the GesturePath object.
private final List<Vector3f> vectors;
/**
* Constructor for the Builder object.
* 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 vectors The list of vectors to add.
* @param input The string to convert
* @return The GesturePath object
*/
public Builder(List<Vector3f> vectors) {
this.vectors = vectors;
}
/**
* Default constructor for the Builder object.
*/
public Builder() {
this.vectors = new ArrayList<>();
}
public static GesturePath fromString(String input) {
byte[] bytes = input.getBytes();
/**
* Adds a vector to the GesturePath object.
*
* @param vector The vector to add.
* @return The Builder object.
*/
public Builder addVector(Vector3f vector) {
vectors.add(vector);
return this;
// 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");
}
Vector3f[] vectors = new Vector3f[input.length() / 12];
/**
* Builds the GesturePath object.
*
* @return The GesturePath object.
*/
public GesturePath build() {
return new GesturePath(vectors.toArray(new Vector3f[0]));
}
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

@@ -0,0 +1,15 @@
package com.example.fitbot.util.processing;
import org.joml.Vector3f;
public interface IInputHandler {
/**
* Function for accepting motion data and the transformed vector.
*
* @param rotationVector The rotation vector of the motion data.
* @param sensorId The sensor ID of the motion data.
*/
void accept(Vector3f rotationVector, int sensorId);
}

View File

@@ -1,16 +0,0 @@
package com.example.fitbot.util.processing;
import org.joml.Vector3f;
public interface IMotionDataConsumer {
/**
* Function for accepting motion data and the transformed vector.
* @param transformedVector The transformed vector.
* @param motionData The input motion data.
* @param sampleIndex The index of the current sample
* @param sampleRate The sample rate.
*/
void accept(Vector3f transformedVector, MotionData motionData, int sampleIndex, double sampleRate, int sensorId);
}

View File

@@ -0,0 +1,337 @@
package com.example.fitbot.util.processing;
import android.util.Log;
import com.example.fitbot.exercise.Exercise;
import com.example.fitbot.exercise.ExerciseManager;
import com.example.fitbot.util.server.WebServer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import org.jetbrains.annotations.NotNull;
import org.joml.Vector3f;
import java.util.ArrayList;
import java.util.List;
public class InputProcessor {
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;
/**
* This field is used to determine if the motion data is being recorded.
* If this is the case, instead of functioning normally, the element
* will record the movement that the user makes, store it in the
* `selfRotationVectorPaths` field and send it to the server.
*/
private boolean recordingMovement = false;
/**
* Represents the duration of the recording in seconds.
* This field only has effect when the `recordingMovement` field is set to true.
*/
private float recordingDurationInSeconds = 0.0f;
// How many seconds have passed since the start of the exercise.
// The exercise starts whenever the `startListening` function is called.
private double secondsPassed = 0.0D;
private long lastTime;
private IInputHandler motionDataConsumer;
private static final String[] REQUIRED_SENSOR_JSON_PROPERTIES =
{"rotationX", "rotationY", "rotationZ", "type", "deviceId"};
// The web server that listens for incoming motion data.
private WebServer server;
/**
* Constructor for the motion processor.
*
* @param paths The target paths of the motion data.
* The length of this array must be equal to the
* amount of sensors available.
* @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)];
targetRotationVectorPaths = paths;
this.sampleRate = inputSampleRate;
this.exerciseDurationInSeconds = exerciseTime;
}
/**
* Function for setting the exercise to use.
* This updates the user and target path and the
* duration of the exercise.
*
* @param exercise The exercise to use the paths for.
*/
public void useExercise(Exercise exercise) {
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.exerciseDurationInSeconds = exercise.exerciseTimeInSeconds;
this.secondsPassed = 0.0D;
this.lastTime = System.currentTimeMillis();
}
/**
* Function for setting whether the motion data
* should be recorded or not.
*
* @param recording Whether the motion data should be recorded.
* @param duration For how long the motion data should be recorded.
* This only has an effect if `recording` is true.
*/
public void setRecording(boolean recording, float duration) {
this.recordingMovement = recording;
this.recordingDurationInSeconds = duration;
if (recording) {
this.secondsPassed = 0.0D;
this.lastTime = System.currentTimeMillis();
}
}
/**
* Function for checking if the exercise or recording has finished.
* This function will return true if the execution of the exercise has finished or
* if the recording has finished, depending on the state of the `recordingMovement` field.
*
* @return Whether the exercise or recording has finished.
*/
public boolean hasFinished() {
return this.recordingMovement ?
(this.secondsPassed >= this.recordingDurationInSeconds) :
(this.secondsPassed >= this.exerciseDurationInSeconds);
}
/**
* Function for starting the listening process
* of the motion sensor. This function will create
* a new WebSocket server and start listening for
* incoming connections.
*/
public void startListening() {
// Create socket server
this.server = WebServer.createServer();
Log.i("MotionProcessor", "Listening for incoming connections.");
// Check if the socket
if (server != null) {
// Update event handler to match our functionality.
server.setEventHandler(this::parsePacket);
this.secondsPassed = 0.0d;
this.lastTime = System.currentTimeMillis();
}
}
/**
* Function for stopping the listening process
* of the motion sensor. This function will stop
* the WebSocket server.
*/
public void stopListening() {
if (server != null) {
server.stop();
server = null;
}
}
/**
* Function for parsing arbitrary packet data.
*
* @param data The data to parse.
*/
public void parsePacket(@NotNull String data) {
try {
Log.i("MotionProcessor", "Received packet data: " + data);
JsonElement json = JsonParser.parseString(data);
if (!json.isJsonObject())
return;
JsonObject object = json.getAsJsonObject();
// Ensure all properties are present in the received JSON object
for (String s : REQUIRED_SENSOR_JSON_PROPERTIES) {
if (!object.has(s))
return;
}
// Parse the data
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();
parseRotationVector(rotation, deviceId);
} catch (Exception e) {
Log.i("MotionProcessor", "Failed to parse packet data.");
}
}
/**
* Function for adding motion data to the processor.
*
* @param rotation The rotation vector of the motion data.
* @param deviceId The device ID of the motion data.
*/
public void parseRotationVector(Vector3f rotation, int deviceId) {
if (deviceId >= 0 && deviceId < selfRotationVectorPaths.length) {
// Re-calculate time for index calculation
secondsPassed = (System.currentTimeMillis() - lastTime) / 1000.0d;
lastTime = System.currentTimeMillis();
// Supposed index of the current rotation vector in the `rotationVectorPaths` array
int selfIndex = (int) (secondsPassed * sampleRate);
motionDataConsumer.accept(rotation, deviceId);
if (selfIndex >= selfRotationVectorPaths[deviceId].length || selfIndex < 0)
return;
selfRotationVectorPaths[deviceId][selfIndex] = rotation;
if ( this.recordingMovement && this.hasFinished()) {
// Do something with the recorded data.
this.recordingMovement = false;
// Convert recorded data from `selfRotationVectorPaths` to string, and
// publish to database, or do something else with it.
String converted = convertRecordedDataToString();
// Do something with it
Log.i("MotionProcessor", "Converted data: ");
Log.i("MotionProcessor", converted);
}
}
}
/**
* Function for converting the recorded data to a string.
* This function will convert the recorded data to a string
* that can be sent to a database or other storage.
*
* @return The converted string.
*/
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
// 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);
// 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];
}
pathBuilder.append(vectorChars);
}
}
// Add a separator between devices
if ( deviceId < selfRotationVectorPaths.length - 1)
pathBuilder.append(ExerciseManager.PATH_DELIMITER);
}
return pathBuilder.toString();
}
/**
* Method for getting the current progress of the exercise.
* The return value will range between 0.0 and 1.0.
*
* @return The current progress of the exercise.
*/
public double getCurrentProgress() {
return secondsPassed / exerciseDurationInSeconds;
}
/**
* Function for setting the motion data receiver.
*
* @param consumer The consumer to set.
*/
public void setInputHandler(IInputHandler consumer) {
if (consumer != null)
this.motionDataConsumer = consumer;
}
/**
* Function for getting the error offsets of the user's path compared to the
* target path at a given point in time.
*
* @param sensorId The sensor ID to get the error offsets from.
* @param time The time to get the error offsets from.
* This value must be >= 0 && <= exerciseTime, otherwise
* the error will be 0 by default.
* @return A list of error offsets of the motion data compared to the reference path.
*/
public double getError(int sensorId, float time) {
// Ensure the sensor ID is within the bounds of the array
if (sensorId < 0 || sensorId >= selfRotationVectorPaths.length)
return 0.0d;
// Index of the current rotation vector
int targetIndex = (int) ((this.exerciseDurationInSeconds / this.targetRotationVectorPaths[sensorId].length) * time);
int selfIndex = (int) (this.selfRotationVectorPaths[sensorId].length / this.sampleRate * time);
// Ensure the indexes are within the bounds of the array
if (
targetIndex >= 0 && targetIndex <= this.targetRotationVectorPaths[sensorId].length - 1 &&
selfIndex >= 0 && selfIndex <= this.selfRotationVectorPaths[sensorId].length - 1 &&
this.selfRotationVectorPaths[sensorId][selfIndex] != null &&
this.targetRotationVectorPaths[sensorId][targetIndex] != null
) {
return this.selfRotationVectorPaths[sensorId][selfIndex].distance(this.targetRotationVectorPaths[sensorId][targetIndex]);
}
return 0.0d;
}
/**
* Method for getting the average error of the motion data
* compared to the reference path.
*
* @param sensorId The sensor ID to get the error offsets from.
* @return The average error of the motion data compared to the reference path.
*/
public double getAverageError(int sensorId) {
double error = 0;
for (int i = 0; i < this.exerciseDurationInSeconds; i++) {
error += getError(sensorId, i);
}
return error / this.exerciseDurationInSeconds;
}
public float secondsPassed() {
return (float) secondsPassed;
}
}

View File

@@ -1,68 +0,0 @@
package com.example.fitbot.util.processing;
import org.joml.Vector3f;
import java.util.Objects;
public class MotionData {
// Data of the motion sensor
public Vector3f acceleration, rotation;
public int sensorId;
// Delimiter for the data received from the motion sensor
private static final String DATA_DELIMITER = ";";
/**
* Constructor for the MotionData class.
*
* @param accelerationX The acceleration in the X axis in m/s^2.
* @param accelerationY The acceleration in the Y axis in m/s^2.
* @param accelerationZ The acceleration in the Z axis in m/s^2.
* @param rotationX The rotation in the X axis in degrees.
* @param rotationY The rotation in the Y axis in degrees.
* @param rotationZ The rotation in the Z axis in degrees.
* @param sensorId The sensor id.
*/
public MotionData(float accelerationX, float accelerationY, float accelerationZ, float rotationX, float rotationY, float rotationZ, int sensorId) {
this(new Vector3f(accelerationX, accelerationY, accelerationZ), new Vector3f(rotationX, rotationY, rotationZ), sensorId);
}
/**
* Constructor for the MotionData class.
*
* @param acceleration The acceleration vector in m/s^2.
* @param rotation The rotation vector in degrees.
*/
public MotionData(Vector3f acceleration, Vector3f rotation, int sensorId) {
this.acceleration = acceleration;
this.rotation = rotation;
this.sensorId = sensorId;
}
/**
* Function for decoding a string into a MotionData object.
* This string must contain the data of the motion sensor
* separated by the delimiter. (;)
*
* @param data The string containing the data of the motion sensor.
* @return An instance of MotionData.
*/
public static MotionData decode(String data) {
Objects.requireNonNull(data); // Ensure data is not null
String[] parts = data.split(DATA_DELIMITER);
if (parts.length != 7)
return null;
return new MotionData(
Float.parseFloat(parts[0]),
Float.parseFloat(parts[1]),
Float.parseFloat(parts[2]),
Float.parseFloat(parts[3]),
Float.parseFloat(parts[4]),
Float.parseFloat(parts[5]),
Integer.parseInt(parts[6])
);
}
}

View File

@@ -1,228 +0,0 @@
package com.example.fitbot.util.processing;
import android.util.Log;
import com.example.fitbot.util.path.GesturePath;
import com.example.fitbot.util.server.WebServer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import org.jetbrains.annotations.NotNull;
import org.joml.Vector3f;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
public class MotionProcessor {
public static final String DELIMITER = ";";
private final List<Vector3f> relativeLeftPath = new ArrayList<>(); // Relative path of the left motion data
private final List<Vector3f> relativeRightPath = new ArrayList<>(); // Relative path of the motion data
private Vector3f ZERO = new Vector3f(0, 0, 0);
private final float sampleRate = 10.0F; // samples/second
private IMotionDataConsumer motionDataConsumer = (p1, p2, p3, p4, p5) -> { };
private WebServer server;
public MotionProcessor() {}
/**
* Function for starting the listening process
* of the motion sensor. This function will create
* a new WebSocket server and start listening for
* incoming connections.
*/
public void startListening() {
// Create socket server
this.server = WebServer.createServer();
Log.i("MotionProcessor", "Listening for incoming connections.");
// Check if the socket
if (server != null) {
// Update event handler to match our functionality.
server.setEventHandler(this::parsePacket);
}
}
/**
* Function for stopping the listening process
* of the motion sensor. This function will stop
* the WebSocket server.
*/
public void stopListening() {
if (server != null) {
server.stop();
}
}
/**
* Function for parsing arbitrary packet data.
*
* @param data The data to parse.
*/
public void parsePacket(@NotNull String data) {
try {
Log.i("MotionProcessor", "Received packet data: " + data);
JsonElement json = JsonParser.parseString(data);
if (!json.isJsonObject())
return;
JsonObject object = json.getAsJsonObject();
String[] required = {
"rotationX", "rotationY", "rotationZ",
"accelerationX", "accelerationY", "accelerationZ",
"type",
"deviceId"
};
// Ensure all properties are present in the received JSON object
for (String s : required) {
if (!object.has(s))
return;
}
// Parse the data
Vector3f rotation = new Vector3f(object.get("rotationX").getAsFloat(), object.get("rotationY").getAsFloat(), object.get("rotationZ").getAsFloat());
Vector3f acceleration = new Vector3f(object.get("accelerationX").getAsFloat(), object.get("accelerationY").getAsFloat(), object.get("accelerationZ").getAsFloat());
int deviceId = object.get("deviceId").getAsInt();
String type = object.get("type").getAsString();
MotionData motionData = new MotionData(rotation, acceleration, deviceId);
if (type.equals("calibrate")) {
ZERO = getRelativeVector(motionData);
return;
}
addMotionData(motionData);
} catch (Exception e) {
// Don't do anything ... just ignore the exception
Log.i("MotionProcessor", "Failed to parse packet data.");
}
}
/**
* Function for adding motion data to the processor.
*
* @param data The motion data to add.
*/
public void addMotionData(MotionData data) {
List<Vector3f> target;
if (data.sensorId == 0)
target = relativeLeftPath;
else target = relativeRightPath;
Vector3f previous = target.isEmpty() ? ZERO : target.get(target.size() - 1);
Vector3f relativeVector = getRelativeVector(data).add(previous);
target.add(relativeVector);
motionDataConsumer.accept(relativeVector, data, target.size(), this.sampleRate, data.sensorId);
}
/**
* Function for updating the relative path.
*
* @param relativeRightPath The new relative path.
*/
public void setRelativePaths(List<Vector3f> relativeLeftPath, List<Vector3f> relativeRightPath) {
this.relativeRightPath.clear();
this.relativeLeftPath.clear();
this.relativeLeftPath.addAll(relativeLeftPath);
this.relativeRightPath.addAll(relativeRightPath);
}
/**
* Function for setting the motion data receiver.
*
* @param consumer The consumer to set.
*/
public void setMotionDataEventHandler(IMotionDataConsumer consumer) {
if (consumer != null)
this.motionDataConsumer = consumer;
}
/**
* Function for getting the relative vector of the motion data.
* This function will calculate the relative position of the motion data
* based on its acceleration and rotation vectors. This has to be done since
* the acceleration vector is relative to its own rotation vector.
*
* @param motionData The motion data to calculate the relative vector for.
* @return The relative vector of the motion data.
*/
public Vector3f getRelativeVector(MotionData motionData) {
// Rotate the acceleration vector back by the rotation vector to make it
// perpendicular to the gravity vector, then apply double integration to get the relative position.
// s = 1/2 * a * t^2
return motionData.acceleration
.rotateX(-motionData.rotation.x)
.rotateY(-motionData.rotation.y)
.rotateZ(-motionData.rotation.z)
.div(2)
.mul(sampleRate * sampleRate);
}
/**
* Function for getting the error offsets of the provided path and the
* received motion data.
*
* @param referencePath The reference path to compare the motion data to.
* @return A list of error offsets of the motion data compared to the reference path.
*/
public List<Double> getErrors(GesturePath referencePath) {
List<Double> errors = new ArrayList<>();
for (Vector3f vector : relativeRightPath) {
errors.add(referencePath.getError(vector));
}
return errors;
}
/**
* Function for getting the error of the motion data compared to the reference path.
*
* @param path The path to compare the motion data to.
* @param referencePoint The reference point to compare the motion data to.
* @return The error of the motion data compared to the reference path.
*/
public double getError(GesturePath path, Vector3f referencePoint) {
return path.getError(referencePoint);
}
/**
* Function for calculating the average error of the motion data
* compared to the reference path.
*
* @param referencePath The reference path to compare the motion data to.
* @return The average error of the motion data compared to the reference path.
*/
public double getAverageError(GesturePath referencePath, int sensorId) {
double error = 0;
for (Double e : getErrors(referencePath)) {
error += e;
}
return error / Math.max(1, (sensorId == 0 ? relativeLeftPath : relativeRightPath).size());
}
/**
* Function for logging the statistics of the motion data.
*
* @param referencePath The reference path to compare the motion data to.
*/
public void logStatistics(GesturePath referencePath) {
Log.i("MotionProcessor", "Path length: " + relativeRightPath.size());
Log.i("MotionProcessor", "Sample rate: " + sampleRate);
Log.i("MotionProcessor", "Calibration point: " + ZERO.toString());
}
}

View File

@@ -1,7 +1,5 @@
package com.example.fitbot.util.server;
import java.net.Socket;
/**
* Interface for handling WebSocket events.
*/

View File

@@ -10,19 +10,16 @@ import java.io.OutputStream;
import java.net.InetSocketAddress;
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 IWebServerHandler eventHandler = (input) -> {}; // No-op.
protected IWebServerHandler eventHandler = (input) -> {
}; // No-op.
private Thread thread;
private AtomicBoolean forceClose = new AtomicBoolean(false);
private final AtomicBoolean forceClose = new AtomicBoolean(false);
/**
* Constructor for creating a new WebSocket server.

View File

@@ -2,11 +2,12 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<corners
android:radius="20dp"
android:radius="40dp"
/>
<gradient
android:startColor="#660000"
android:endColor="#990000"
android:startColor="#990000"
android:endColor="#FF0000"
android:angle="90"/>
</shape>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/lightBlue" />
<stroke android:width="2dp" android:color="#FF0000" />
<corners android:radius="20dp" />
</shape>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/midBlue" />
<stroke android:width="2dp" android:color="#FF0000" />
<corners android:radius="10dp" />
</shape>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/midderBlue" />
<corners android:radius="10dp" />
</shape>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/darkBlue" />
<stroke android:width="4dp" android:color="@color/white" />
<corners android:radius="8dp" />
</shape>

View File

@@ -0,0 +1,74 @@
<?xml version="1.0" encoding="utf-8"?>
<vector
android:height="108dp"
android:width="108dp"
android:viewportHeight="108"
android:viewportWidth="108"
xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#3DDC84"
android:pathData="M0,0h108v108h-108z"/>
<path android:fillColor="#00000000" android:pathData="M9,0L9,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,0L19,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M29,0L29,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M39,0L39,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M49,0L49,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M59,0L59,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M69,0L69,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M79,0L79,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M89,0L89,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M99,0L99,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,9L108,9"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,19L108,19"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,29L108,29"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,39L108,39"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,49L108,49"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,59L108,59"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,69L108,69"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,79L108,79"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,89L108,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,99L108,99"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,29L89,29"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,39L89,39"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,49L89,49"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,59L89,59"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,69L89,69"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,79L89,79"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M29,19L29,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M39,19L39,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M49,19L49,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M59,19L59,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M69,19L69,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M79,19L79,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
</vector>

View File

@@ -0,0 +1,5 @@
<vector android:height="48dp" android:tint="?attr/colorControlNormal"
android:viewportHeight="24" android:viewportWidth="24"
android:width="48dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M9,16.17L4.83,12l-1.42,1.41L9,19 21,7l-1.41,-1.41z"/>
</vector>

View File

@@ -0,0 +1,5 @@
<vector android:height="48dp" android:tint="?attr/colorControlNormal"
android:viewportHeight="24" android:viewportWidth="24"
android:width="48dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z"/>
</vector>

View File

@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM13,17h-2v-6h2v6zM13,9h-2L11,7h2v2z"/>
</vector>

View File

@@ -0,0 +1,5 @@
<vector android:height="40dp" android:tint="?attr/colorControlNormal"
android:viewportHeight="24" android:viewportWidth="24"
android:width="40dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM13,17h-2v-6h2v6zM13,9h-2L11,7h2v2z"/>
</vector>

View File

@@ -0,0 +1,5 @@
<vector android:height="48dp" android:tint="?attr/colorControlNormal"
android:viewportHeight="24" android:viewportWidth="24"
android:width="48dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM13,17h-2v-6h2v6zM13,9h-2L11,7h2v2z"/>
</vector>

View File

@@ -0,0 +1,5 @@
<vector android:height="48dp" android:tint="?attr/colorControlNormal"
android:viewportHeight="24" android:viewportWidth="24"
android:width="48dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M6,18l8.5,-6L6,6v12zM16,6v12h2V6h-2z"/>
</vector>

View File

@@ -1,170 +1,74 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
<vector
android:height="108dp"
android:width="108dp"
android:viewportHeight="108"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillColor="#FF0000"
xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#D10000"
android:pathData="M0,0h108v108h-108z"/>
<path
android:fillColor="#00000000"
android:pathData="M9,0L9,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,0L19,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,0L29,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,0L39,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,0L49,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,0L59,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,0L69,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,0L79,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M89,0L89,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M99,0L99,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,9L108,9"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,19L108,19"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,29L108,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,39L108,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,49L108,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,59L108,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,69L108,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,79L108,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,89L108,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,99L108,99"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,29L89,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,39L89,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,49L89,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,59L89,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,69L89,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,79L89,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,19L29,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,19L39,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,19L49,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,19L59,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,19L69,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,19L79,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path android:fillColor="#00000000" android:pathData="M9,0L9,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,0L19,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M29,0L29,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M39,0L39,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M49,0L49,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M59,0L59,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M69,0L69,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M79,0L79,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M89,0L89,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M99,0L99,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,9L108,9"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,19L108,19"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,29L108,29"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,39L108,39"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,49L108,49"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,59L108,59"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,69L108,69"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,79L108,79"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,89L108,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,99L108,99"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,29L89,29"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,39L89,39"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,49L89,49"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,59L89,59"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,69L89,69"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,79L89,79"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M29,19L29,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M39,19L39,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M49,19L49,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M59,19L59,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M69,19L69,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M79,19L79,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
</vector>

View File

@@ -1,10 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/listview_background_shape">
<stroke android:width="2dp" android:color="#ff207d94" />
<padding android:left="2dp"
android:top="2dp"
android:right="2dp"
android:bottom="2dp" />
<corners android:radius="45dp" />
<solid android:color="#F0F0F0" />
</shape>

View File

@@ -9,4 +9,5 @@
android:startColor="#990000"
android:endColor="#FF0000"
android:angle="90"/>
</shape>

View File

@@ -1,77 +1,113 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:background="@color/red"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.activities.EndScreenActivity">
android:background="@color/darkBlue"
tools:context=".ui.activities.HelpActivity">
<View
android:id="@+id/myRectangleView"
android:layout_width="720dp"
android:layout_height="270dp"
android:layout_marginStart="320dp"
android:layout_marginTop="25dp"
android:layout_marginEnd="320dp"
android:layout_marginBottom="25dp"
android:background="@drawable/rectangle"
app:layout_constraintBottom_toTopOf="@+id/homeButton"
app:layout_constraintEnd_toEndOf="parent"
<LinearLayout
android:layout_width="800dp"
android:layout_height="450dp"
android:layout_marginStart="80dp"
android:layout_marginTop="24dp"
android:background="@drawable/border_background_2"
android:orientation="vertical"
android:padding="16dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
app:layout_constraintTop_toTopOf="parent">
<Button
android:id="@+id/homeButton"
style="@style/ButtonStyle"
android:layout_width="278dp"
android:layout_height="117dp"
android:layout_marginStart="501dp"
android:layout_marginTop="25dp"
android:layout_marginEnd="501dp"
android:layout_marginBottom="50dp"
android:text="Home"
app:layout_constraintBottom_toTopOf="@+id/continueButton"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/myRectangleView" />
<Button
android:id="@+id/continueButton"
style="@style/ButtonStyle"
android:layout_width="280dp"
android:layout_height="120dp"
android:layout_marginStart="500dp"
android:layout_marginTop="25dp"
android:layout_marginEnd="500dp"
android:layout_marginBottom="140dp"
android:text="Continue"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/homeButton" />
<TextView
android:id="@+id/gefeliciteerdText"
style="@style/TextStyle"
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Gefeliciteerd"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_gravity="center"
android:layout_marginTop="20dp"
android:layout_marginBottom="40dp"
android:background="@drawable/border_background"
android:orientation="vertical"
android:paddingVertical="15dp"
android:paddingHorizontal="20dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.155" />
app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="@+id/workoutText"
style="@style/TextStyle"
android:layout_width="wrap_content"
android:id="@+id/textViewTitleEndScreen"
style="@style/TextStyleTitle"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="@string/afgerond" />
</LinearLayout>
<LinearLayout
android:layout_width="700dp"
android:layout_height="wrap_content"
android:text="u heeft de work out voltooid"
app:layout_constraintBottom_toBottomOf="@+id/myRectangleView"
app:layout_constraintEnd_toEndOf="parent"
android:layout_gravity="center"
android:layout_marginTop="10dp"
android:background="@drawable/border_background"
android:orientation="vertical"
android:padding="16dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/myRectangleView" />
app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="@+id/textViewDescriptionEndScreen"
style="@style/TextStyle"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="@string/voltooid"
android:textAlignment="center"/>
</LinearLayout>
<LinearLayout
android:layout_width="700dp"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="10dp"
android:background="@drawable/border_background"
android:orientation="vertical"
android:padding="16dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="@+id/textViewScoreEndScreen"
style="@style/TextStyle"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="@string/score"
android:textAlignment="center"/>
</LinearLayout>
</LinearLayout>
<Button
android:id="@+id/homeButtonEndScreen"
android:layout_width="150dp"
android:layout_height="75dp"
android:layout_marginEnd="280dp"
android:layout_marginBottom="30dp"
android:background="@drawable/red_button_gradient"
android:drawableTop="@drawable/ic_baseline_home_48"
android:drawableTint="@color/white"
android:padding="15dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<Button
android:id="@+id/startButtonEndScreen"
android:layout_width="150dp"
android:layout_height="75dp"
android:layout_marginStart="280dp"
android:layout_marginBottom="30dp"
android:background="@drawable/red_button_gradient"
android:padding="15dp"
android:text="@string/start"
android:textAllCaps="false"
android:textColor="@color/white"
android:textSize="30sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</android.support.constraint.ConstraintLayout>

View File

@@ -5,79 +5,166 @@ xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/black"
android:background="@color/darkBlue"
android:fitsSystemWindows="true"
android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
tools:context=".ui.activities.FitnessActivity"
tools:openDrawer="start">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="20dp"
android:background="@drawable/border_background_2"
android:orientation="horizontal"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.505"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<RelativeLayout
android:layout_width="410dp"
android:layout_height="410dp"
android:layout_marginVertical="20dp"
android:layout_marginLeft="30dp"
android:layout_marginRight="15dp"
android:background="@drawable/border_background"
android:layout_gravity="center">
<Button
android:id="@+id/infoButtonFitness"
android:layout_width="45dp"
android:layout_height="45dp"
android:background="@drawable/red_button_gradient"
android:drawableTop="@drawable/ic_baseline_info_40"
android:drawableTint="@color/white"
android:padding="2.5dp"
android:layout_margin="10dp"
tools:ignore="SpeakableTextPresentCheck" />
<VideoView
android:id="@+id/videoView"
android:layout_width="450dp"
android:layout_height="450dp"
android:layout_width="300dp"
android:layout_height="300dp"
android:layout_centerInParent="true"
android:layout_margin="50dp" />
</RelativeLayout>
<LinearLayout
android:layout_width="410dp"
android:layout_height="410dp"
android:layout_gravity="center"
android:layout_marginVertical="20dp"
android:layout_marginLeft="15dp"
android:layout_marginRight="30dp"
android:background="@drawable/border_background"
android:orientation="vertical"
android:padding="20dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="@+id/textViewFitnessTitle"
style="@style/TextStyleTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/title"
android:textAlignment="center"
android:layout_gravity="center_horizontal"
android:layout_marginBottom="5dp"/>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/border_background_3"
android:padding="16dp"
android:layout_margin="10dp"
android:layout_gravity="center_horizontal">
<TextView
android:id="@+id/textViewFitnessDescription"
style="@style/TextStyle"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="@string/context"
android:textAlignment="center" />
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/border_background_3"
android:padding="16dp"
android:layout_margin="10dp"
android:layout_gravity="center_horizontal">
<TextView
android:id="@+id/textViewFitnessBar"
style="@style/TextStyle"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="@string/context"
android:textAlignment="center" />
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/border_background_3"
android:padding="16dp"
android:layout_margin="10dp"
android:layout_gravity="center_horizontal">
<TextView
android:id="@+id/textViewFitnessScore"
style="@style/TextStyle"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="@string/context"
android:textAlignment="center" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
<Button
android:id="@+id/homeButtonFitness"
android:layout_width="150dp"
android:layout_height="75dp"
android:layout_marginStart="200dp"
android:layout_marginBottom="30dp"
android:background="@drawable/red_button_gradient"
android:drawableTop="@drawable/ic_baseline_home_48"
android:drawableTint="@color/white"
android:padding="15dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
tools:ignore="SpeakableTextPresentCheck" />
<Button
android:id="@+id/skipButtonFitness"
android:layout_width="150dp"
android:layout_height="75dp"
android:layout_marginEnd="200dp"
android:layout_marginBottom="30dp"
android:background="@drawable/red_button_gradient"
android:drawableTop="@drawable/ic_baseline_skip_next_48"
android:drawableTint="@color/white"
android:padding="15dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.04"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.2" />
tools:ignore="SpeakableTextPresentCheck" />
<com.example.fitbot.ui.components.PersonalMotionPreviewElement
<com.example.fitbot.ui.components.ExerciseStatusElement
android:id="@+id/personalMotionPreviewElement"
android:layout_width="450dp"
android:layout_height="450dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.96"
android:layout_width="10dp"
android:layout_height="10dp"
android:layout_marginStart="10dp"
android:layout_marginTop="10dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.2" />
<Button
android:id="@+id/homeButton"
android:layout_width="150dp"
android:layout_height="75dp"
android:background="@drawable/red_button_gradient"
android:text="@string/home"
android:textAllCaps="false"
android:textColor="@color/white"
android:textSize="30sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.02"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.968" />
<Button
android:id="@+id/skipButton"
android:layout_width="150dp"
android:layout_height="75dp"
android:background="@drawable/red_button_gradient"
android:text="@string/skip"
android:textAllCaps="false"
android:textColor="@color/white"
android:textSize="30sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.968" />
<Button
android:id="@+id/buttonComplete"
android:layout_width="150dp"
android:layout_height="75dp"
android:background="@drawable/red_button_gradient"
android:text="@string/complete"
android:textAllCaps="false"
android:textColor="@color/white"
android:textSize="30sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.979"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.968" />
app:layout_constraintTop_toTopOf="parent" />
</android.support.constraint.ConstraintLayout>

View File

@@ -4,61 +4,98 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/black"
android:background="@color/darkBlue"
android:fitsSystemWindows="true"
tools:context=".ui.activities.HelpActivity">
<View
android:id="@+id/myRectangleView"
android:layout_width="1075dp"
android:layout_height="510dp"
android:background="@drawable/rectangle"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.502"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.168"
tools:ignore="MissingConstraints" />
<Button
android:id="@+id/homeButton"
android:layout_width="200dp"
android:id="@+id/homeButtonHelp"
android:layout_width="150dp"
android:layout_height="75dp"
android:layout_marginStart="406dp"
android:layout_marginEnd="406dp"
android:layout_marginBottom="30dp"
android:background="@drawable/red_button_gradient"
android:text="@string/home"
android:textAllCaps="false"
android:textColor="@color/white"
android:textSize="30sp"
android:drawableTop="@drawable/ic_baseline_home_48"
android:drawableTint="@color/white"
android:padding="15dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<LinearLayout
android:layout_width="800dp"
android:layout_height="450dp"
android:layout_marginStart="80dp"
android:layout_marginTop="24dp"
android:background="@drawable/border_background_2"
android:orientation="vertical"
android:padding="16dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.852" />
app:layout_constraintTop_toTopOf="parent">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="20dp"
android:layout_marginBottom="40dp"
android:background="@drawable/border_background"
android:orientation="vertical"
android:paddingVertical="15dp"
android:paddingHorizontal="20dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="@+id/textView4.2"
style="@style/TextStyle"
android:layout_width="1053dp"
android:layout_height="191dp"
android:text="@string/uitleg"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.502"
android:id="@+id/textViewTitleHelp"
style="@style/TextStyleTitle"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="@string/help" />
</LinearLayout>
<LinearLayout
android:layout_width="700dp"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="10dp"
android:background="@drawable/border_background"
android:orientation="vertical"
android:padding="16dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.133" />
app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="@+id/textView4"
android:id="@+id/textViewStartHelp"
style="@style/TextStyle"
android:layout_width="1053dp"
android:layout_height="191dp"
android:text="Als je klaar bent kunt u op de COMPLETE knop drukken in het sport scherm en dan kunt u uw punten inzien"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.502"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="@string/uitlegStart"
android:textAlignment="center"/>
</LinearLayout>
<LinearLayout
android:layout_width="700dp"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="10dp"
android:background="@drawable/border_background"
android:orientation="vertical"
android:padding="16dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.482" />
app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="@+id/textViewHomeHelp"
style="@style/TextStyle"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="@string/uitlegHome"
android:textAlignment="center"/>
</LinearLayout>
</LinearLayout>
</android.support.constraint.ConstraintLayout>

View File

@@ -4,9 +4,7 @@
android:id="@+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#232323"
android:fitsSystemWindows="true"
android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
tools:context=".ui.activities.MainActivity"
tools:openDrawer="start">
@@ -26,66 +24,95 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/textView2"
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="232dp"
android:layout_marginStart="215dp"
android:layout_marginTop="120dp"
android:text="@string/welkom_bij_fitbot"
android:textColor="@color/white"
android:textSize="64sp"
android:layout_marginEnd="215dp"
android:layout_marginBottom="23dp"
android:background="@drawable/box_background"
android:orientation="vertical"
android:padding="16dp"
app:layout_constraintBottom_toTopOf="@+id/startButtonMain"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="@+id/textView3"
android:id="@+id/textViewWelkom"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/welkom_bij_fitbot"
android:textColor="@color/white"
android:textSize="64sp" />
<TextView
android:id="@+id/textViewFit"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="240dp"
android:layout_marginTop="200dp"
android:text="@string/robot_helpt"
android:textColor="@color/white"
android:textSize="32sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
android:translationX="8dp" />
</LinearLayout>
<Button
android:id="@+id/startButton"
android:layout_width="200dp"
android:layout_height="100dp"
android:layout_marginStart="380dp"
android:layout_marginTop="280dp"
android:background="@drawable/red_button_gradient"
android:id="@+id/startButtonMain"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="365dp"
android:layout_marginTop="315dp"
android:layout_marginEnd="365dp"
android:layout_marginBottom="22dp"
android:background="@drawable/big_red_button_gradient"
android:gravity="center"
android:paddingHorizontal="25dp"
android:paddingVertical="5dp"
android:text="@string/start"
android:textAlignment="center"
android:textAllCaps="false"
android:textColor="@color/white"
android:textSize="80sp"
app:layout_constraintBottom_toTopOf="@+id/helpButtonMain"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.727" />
<Button
android:id="@+id/helpButton"
android:layout_width="100dp"
android:layout_height="50dp"
android:layout_marginStart="428dp"
android:layout_marginTop="400dp"
android:background="@drawable/darkred_button_gradient"
android:id="@+id/helpButtonMain"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="420dp"
android:layout_marginTop="444dp"
android:layout_marginEnd="420dp"
android:background="@drawable/red_button_gradient"
android:gravity="center"
android:paddingHorizontal="20dp"
android:paddingVertical="5dp"
android:text="@string/help"
android:textAlignment="center"
android:textAllCaps="false"
android:textColor="@color/white"
android:textSize="40sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/imageView2"
android:layout_width="200dp"
android:layout_height="200dp"
android:layout_marginTop="340dp"
android:contentDescription="@string/robot_logo"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.258" />
<ImageView
android:id="@+id/imageViewRobotLogo"
android:layout_width="200dp"
android:layout_height="200dp"
android:layout_marginStart="8dp"
android:layout_marginBottom="4dp"
android:contentDescription="@string/robot_logo"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:srcCompat="@drawable/robot_logo_inverted" />
@@ -96,7 +123,11 @@
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
android:background="@color/darkBlue"
app:headerLayout="@layout/header"
app:itemIconTint="@color/white"
app:itemTextColor="@color/white"
app:menu="@menu/main_menu" />
</android.support.v4.widget.DrawerLayout>

View File

@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="800dp"
android:layout_height="wrap_content"
android:background="@drawable/red_button_gradient"
android:orientation="vertical"
android:fitsSystemWindows="true">
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TextView
android:id="@+id/textViewTitleDialog"
style="@style/TextStyleTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:layout_marginTop="10dp"
android:text="@string/descriptionTitle" />
<Button
android:id="@+id/closeButtonDialog"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_alignParentEnd="true"
android:layout_marginTop="20dp"
android:layout_marginEnd="10dp"
android:background="@color/transparent"
android:drawableTop="@drawable/ic_baseline_close_48"
android:drawableTint="@color/white" />
</RelativeLayout>
<TextView
android:id="@+id/textViewDialog"
style="@style/TextStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxWidth="800dp"
android:text="@string/description"
android:layout_marginHorizontal="10dp"
android:layout_marginBottom="10dp"/>
</LinearLayout>

View File

@@ -24,7 +24,7 @@
android:layout_marginTop="60dp"
android:text="FitBot"
android:textSize="48sp"
android:textColor="@color/black"
android:textColor="@color/darkBlue"
app:layout_constraintStart_toEndOf="@+id/imageView"
app:layout_constraintTop_toTopOf="parent" />

View File

@@ -3,6 +3,6 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#00000000"
android:elevation="8dp">
android:elevation="8dp"
android:theme="@style/ToolbarNav">
</android.support.v7.widget.Toolbar>

View File

@@ -12,9 +12,6 @@
<group android:checkableBehavior="single">
<item android:title="Options">
<menu>
<item
android:id="@+id/nav_settings"
android:icon="@drawable/ic_baseline_settings_48"
@@ -23,9 +20,6 @@
android:id="@+id/nav_rate"
android:icon="@drawable/ic_baseline_star_rate_48"
android:title="Rate" />
</menu>
</item>
</group>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/fitbot_launcher_background"/>
<foreground android:drawable="@mipmap/fitbot_launcher_foreground"/>
</adaptive-icon>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/fitbot_launcher_background"/>
<foreground android:drawable="@mipmap/fitbot_launcher_foreground"/>
</adaptive-icon>

View File

@@ -1,5 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

View File

@@ -1,5 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

View File

@@ -0,0 +1 @@
<?xml version="1.0" encoding="utf-8"?><Animation xmlns:editor="http://www.aldebaran.com/animation/editor" typeVersion="2.0"/>

View File

@@ -0,0 +1,99 @@
<?xml version="1.0" encoding="UTF-8"?>
<Animation xmlns:editor="http://www.ald.softbankrobotics.com/animation/editor" typeVersion="2.0" editor:fps="25">
<ActuatorCurve fps="25" actuator="LShoulderPitch" mute="false" unit="degree">
<Key value="11.4200077" frame="20">
<Tangent side="right" abscissaParam="8.33333333" ordinateParam="0" editor:interpType="bezier_auto"/>
</Key>
<Key value="58.7350922" frame="45">
<Tangent side="left" abscissaParam="-8.33333333" ordinateParam="0" editor:interpType="bezier_auto"/>
</Key>
</ActuatorCurve>
<ActuatorCurve fps="25" actuator="LShoulderRoll" mute="false" unit="degree">
<Key value="6.67969275" frame="20">
<Tangent side="right" abscissaParam="8.33333333" ordinateParam="0" editor:interpType="bezier_auto"/>
</Key>
<Key value="6.67969275" frame="45">
<Tangent side="left" abscissaParam="-8.33333333" ordinateParam="0" editor:interpType="bezier_auto"/>
</Key>
</ActuatorCurve>
<ActuatorCurve fps="25" actuator="LElbowYaw" mute="false" unit="degree">
<Key value="-88.3528824" frame="20">
<Tangent side="right" abscissaParam="8.33333333" ordinateParam="0" editor:interpType="bezier_auto"/>
</Key>
<Key value="-88.3528824" frame="45">
<Tangent side="left" abscissaParam="-8.33333333" ordinateParam="0" editor:interpType="bezier_auto"/>
</Key>
</ActuatorCurve>
<ActuatorCurve fps="25" actuator="LElbowRoll" mute="false" unit="degree">
<Key value="-5.89348125" frame="20">
<Tangent side="right" abscissaParam="8.33333333" ordinateParam="0" editor:interpType="bezier_auto"/>
</Key>
<Key value="-89.5" frame="45">
<Tangent side="left" abscissaParam="-8.33333333" ordinateParam="0" editor:interpType="bezier_auto"/>
</Key>
</ActuatorCurve>
<ActuatorCurve fps="25" actuator="LWristYaw" mute="false" unit="degree">
<Key value="-1.76023543" frame="20">
<Tangent side="right" abscissaParam="8.33333333" ordinateParam="0" editor:interpType="bezier_auto"/>
</Key>
<Key value="-1.76023543" frame="45">
<Tangent side="left" abscissaParam="-8.33333333" ordinateParam="0" editor:interpType="bezier_auto"/>
</Key>
</ActuatorCurve>
<ActuatorCurve fps="25" actuator="LHand" mute="false" unit="dimensionless">
<Key value="0.589630966" frame="20">
<Tangent side="right" abscissaParam="8.33333333" ordinateParam="0" editor:interpType="bezier_auto"/>
</Key>
<Key value="0.589630966" frame="45">
<Tangent side="left" abscissaParam="-8.33333333" ordinateParam="0" editor:interpType="bezier_auto"/>
</Key>
</ActuatorCurve>
<ActuatorCurve fps="25" actuator="RShoulderPitch" mute="false" unit="degree">
<Key value="15.1628304" frame="20">
<Tangent side="right" abscissaParam="8.33333333" ordinateParam="0" editor:interpType="bezier_auto"/>
</Key>
<Key value="62.0810318" frame="45">
<Tangent side="left" abscissaParam="-8.33333333" ordinateParam="0" editor:interpType="bezier_auto"/>
</Key>
</ActuatorCurve>
<ActuatorCurve fps="25" actuator="RShoulderRoll" mute="false" unit="degree">
<Key value="-6.59180212" frame="20">
<Tangent side="right" abscissaParam="8.33333333" ordinateParam="0" editor:interpType="bezier_auto"/>
</Key>
<Key value="-6.59180212" frame="45">
<Tangent side="left" abscissaParam="-8.33333333" ordinateParam="0" editor:interpType="bezier_auto"/>
</Key>
</ActuatorCurve>
<ActuatorCurve fps="25" actuator="RElbowYaw" mute="false" unit="degree">
<Key value="96.1428452" frame="20">
<Tangent side="right" abscissaParam="8.33333333" ordinateParam="0" editor:interpType="bezier_auto"/>
</Key>
<Key value="96.1428452" frame="45">
<Tangent side="left" abscissaParam="-8.33333333" ordinateParam="0" editor:interpType="bezier_auto"/>
</Key>
</ActuatorCurve>
<ActuatorCurve fps="25" actuator="RElbowRoll" mute="false" unit="degree">
<Key value="0.5" frame="20">
<Tangent side="right" abscissaParam="8.33333333" ordinateParam="0" editor:interpType="bezier_auto"/>
</Key>
<Key value="89.5" frame="45">
<Tangent side="left" abscissaParam="-8.33333333" ordinateParam="0" editor:interpType="bezier_auto"/>
</Key>
</ActuatorCurve>
<ActuatorCurve fps="25" actuator="RWristYaw" mute="false" unit="degree">
<Key value="1.57964516" frame="20">
<Tangent side="right" abscissaParam="8.33333333" ordinateParam="0" editor:interpType="bezier_auto"/>
</Key>
<Key value="1.57964516" frame="45">
<Tangent side="left" abscissaParam="-8.33333333" ordinateParam="0" editor:interpType="bezier_auto"/>
</Key>
</ActuatorCurve>
<ActuatorCurve fps="25" actuator="RHand" mute="false" unit="dimensionless">
<Key value="0.588752136" frame="20">
<Tangent side="right" abscissaParam="8.33333333" ordinateParam="0" editor:interpType="bezier_auto"/>
</Key>
<Key value="0.588752136" frame="45">
<Tangent side="left" abscissaParam="-8.33333333" ordinateParam="0" editor:interpType="bezier_auto"/>
</Key>
</ActuatorCurve>
</Animation>

View File

@@ -0,0 +1,147 @@
<?xml version="1.0" encoding="UTF-8"?>
<Animation xmlns:editor="http://www.ald.softbankrobotics.com/animation/editor" typeVersion="2.0" editor:fps="25">
<ActuatorCurve fps="25" actuator="LShoulderPitch" mute="false" unit="degree">
<Key value="69.5353165" frame="0">
<Tangent side="right" abscissaParam="9.66666667" ordinateParam="0" editor:interpType="bezier_auto"/>
</Key>
<Key value="-74.2801361" frame="29">
<Tangent side="left" abscissaParam="-9.66666667" ordinateParam="0" editor:interpType="bezier_auto"/>
<Tangent side="right" abscissaParam="11.3333333" ordinateParam="0" editor:interpType="bezier_auto"/>
</Key>
<Key value="69.5353165" frame="63">
<Tangent side="left" abscissaParam="-11.3333333" ordinateParam="0" editor:interpType="bezier_auto"/>
</Key>
</ActuatorCurve>
<ActuatorCurve fps="25" actuator="LShoulderRoll" mute="false" unit="degree">
<Key value="6.67969275" frame="0">
<Tangent side="right" abscissaParam="9.66666667" ordinateParam="0" editor:interpType="bezier_auto"/>
</Key>
<Key value="6.67969275" frame="29">
<Tangent side="left" abscissaParam="-9.66666667" ordinateParam="0" editor:interpType="bezier_auto"/>
<Tangent side="right" abscissaParam="11.3333333" ordinateParam="0" editor:interpType="bezier_auto"/>
</Key>
<Key value="6.67969275" frame="63">
<Tangent side="left" abscissaParam="-11.3333333" ordinateParam="0" editor:interpType="bezier_auto"/>
</Key>
</ActuatorCurve>
<ActuatorCurve fps="25" actuator="LElbowYaw" mute="false" unit="degree">
<Key value="-69.7851486" frame="0">
<Tangent side="right" abscissaParam="9.66666667" ordinateParam="0" editor:interpType="bezier_auto"/>
</Key>
<Key value="-69.7851486" frame="29">
<Tangent side="left" abscissaParam="-9.66666667" ordinateParam="0" editor:interpType="bezier_auto"/>
<Tangent side="right" abscissaParam="11.3333333" ordinateParam="0" editor:interpType="bezier_auto"/>
</Key>
<Key value="-69.7851486" frame="63">
<Tangent side="left" abscissaParam="-11.3333333" ordinateParam="0" editor:interpType="bezier_auto"/>
</Key>
</ActuatorCurve>
<ActuatorCurve fps="25" actuator="LElbowRoll" mute="false" unit="degree">
<Key value="-89.5" frame="0">
<Tangent side="right" abscissaParam="9.66666667" ordinateParam="0" editor:interpType="bezier_auto"/>
</Key>
<Key value="-0.5" frame="29">
<Tangent side="left" abscissaParam="-9.66666667" ordinateParam="0" editor:interpType="bezier_auto"/>
<Tangent side="right" abscissaParam="11.3333333" ordinateParam="0" editor:interpType="bezier_auto"/>
</Key>
<Key value="-89.5" frame="63">
<Tangent side="left" abscissaParam="-11.3333333" ordinateParam="0" editor:interpType="bezier_auto"/>
</Key>
</ActuatorCurve>
<ActuatorCurve fps="25" actuator="LWristYaw" mute="false" unit="degree">
<Key value="-1.76023543" frame="0">
<Tangent side="right" abscissaParam="9.66666667" ordinateParam="0" editor:interpType="bezier_auto"/>
</Key>
<Key value="-1.76023543" frame="29">
<Tangent side="left" abscissaParam="-9.66666667" ordinateParam="0" editor:interpType="bezier_auto"/>
<Tangent side="right" abscissaParam="11.3333333" ordinateParam="0" editor:interpType="bezier_auto"/>
</Key>
<Key value="-1.76023543" frame="63">
<Tangent side="left" abscissaParam="-11.3333333" ordinateParam="0" editor:interpType="bezier_auto"/>
</Key>
</ActuatorCurve>
<ActuatorCurve fps="25" actuator="LHand" mute="false" unit="dimensionless">
<Key value="0.589630966" frame="0">
<Tangent side="right" abscissaParam="9.66666667" ordinateParam="0" editor:interpType="bezier_auto"/>
</Key>
<Key value="0.589630966" frame="29">
<Tangent side="left" abscissaParam="-9.66666667" ordinateParam="0" editor:interpType="bezier_auto"/>
<Tangent side="right" abscissaParam="11.3333333" ordinateParam="0" editor:interpType="bezier_auto"/>
</Key>
<Key value="0.589630966" frame="63">
<Tangent side="left" abscissaParam="-11.3333333" ordinateParam="0" editor:interpType="bezier_auto"/>
</Key>
</ActuatorCurve>
<ActuatorCurve fps="25" actuator="RShoulderPitch" mute="false" unit="degree">
<Key value="68.6139908" frame="0">
<Tangent side="right" abscissaParam="9.66666667" ordinateParam="0" editor:interpType="bezier_auto"/>
</Key>
<Key value="-72.7772903" frame="29">
<Tangent side="left" abscissaParam="-9.66666667" ordinateParam="0" editor:interpType="bezier_auto"/>
<Tangent side="right" abscissaParam="11.3333333" ordinateParam="0" editor:interpType="bezier_auto"/>
</Key>
<Key value="68.6139908" frame="63">
<Tangent side="left" abscissaParam="-11.3333333" ordinateParam="0" editor:interpType="bezier_auto"/>
</Key>
</ActuatorCurve>
<ActuatorCurve fps="25" actuator="RShoulderRoll" mute="false" unit="degree">
<Key value="-6.59180212" frame="0">
<Tangent side="right" abscissaParam="9.66666667" ordinateParam="0" editor:interpType="bezier_auto"/>
</Key>
<Key value="-6.59180212" frame="29">
<Tangent side="left" abscissaParam="-9.66666667" ordinateParam="0" editor:interpType="bezier_auto"/>
<Tangent side="right" abscissaParam="11.3333333" ordinateParam="0" editor:interpType="bezier_auto"/>
</Key>
<Key value="-6.59180212" frame="63">
<Tangent side="left" abscissaParam="-11.3333333" ordinateParam="0" editor:interpType="bezier_auto"/>
</Key>
</ActuatorCurve>
<ActuatorCurve fps="25" actuator="RElbowYaw" mute="false" unit="degree">
<Key value="96.1428452" frame="0">
<Tangent side="right" abscissaParam="9.66666667" ordinateParam="0" editor:interpType="bezier_auto"/>
</Key>
<Key value="96.1428452" frame="29">
<Tangent side="left" abscissaParam="-9.66666667" ordinateParam="0" editor:interpType="bezier_auto"/>
<Tangent side="right" abscissaParam="11.3333333" ordinateParam="0" editor:interpType="bezier_auto"/>
</Key>
<Key value="96.1428452" frame="63">
<Tangent side="left" abscissaParam="-11.3333333" ordinateParam="0" editor:interpType="bezier_auto"/>
</Key>
</ActuatorCurve>
<ActuatorCurve fps="25" actuator="RElbowRoll" mute="false" unit="degree">
<Key value="89.5" frame="0">
<Tangent side="right" abscissaParam="9.66666667" ordinateParam="0" editor:interpType="bezier_auto"/>
</Key>
<Key value="0.5" frame="29">
<Tangent side="left" abscissaParam="-9.66666667" ordinateParam="0" editor:interpType="bezier_auto"/>
<Tangent side="right" abscissaParam="11.3333333" ordinateParam="0" editor:interpType="bezier_auto"/>
</Key>
<Key value="89.5" frame="63">
<Tangent side="left" abscissaParam="-11.3333333" ordinateParam="0" editor:interpType="bezier_auto"/>
</Key>
</ActuatorCurve>
<ActuatorCurve fps="25" actuator="RWristYaw" mute="false" unit="degree">
<Key value="1.57964516" frame="0">
<Tangent side="right" abscissaParam="9.66666667" ordinateParam="0" editor:interpType="bezier_auto"/>
</Key>
<Key value="1.57964516" frame="29">
<Tangent side="left" abscissaParam="-9.66666667" ordinateParam="0" editor:interpType="bezier_auto"/>
<Tangent side="right" abscissaParam="11.3333333" ordinateParam="0" editor:interpType="bezier_auto"/>
</Key>
<Key value="1.57964516" frame="63">
<Tangent side="left" abscissaParam="-11.3333333" ordinateParam="0" editor:interpType="bezier_auto"/>
</Key>
</ActuatorCurve>
<ActuatorCurve fps="25" actuator="RHand" mute="false" unit="dimensionless">
<Key value="0.588752136" frame="0">
<Tangent side="right" abscissaParam="9.66666667" ordinateParam="0" editor:interpType="bezier_auto"/>
</Key>
<Key value="0.588752136" frame="29">
<Tangent side="left" abscissaParam="-9.66666667" ordinateParam="0" editor:interpType="bezier_auto"/>
<Tangent side="right" abscissaParam="11.3333333" ordinateParam="0" editor:interpType="bezier_auto"/>
</Key>
<Key value="0.588752136" frame="63">
<Tangent side="left" abscissaParam="-11.3333333" ordinateParam="0" editor:interpType="bezier_auto"/>
</Key>
</ActuatorCurve>
</Animation>

View File

@@ -7,4 +7,13 @@
<color name="teal_700">#FF018786</color>
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
<color name="darkBlue">#1C1C27</color>
<color name="midBlue">#24242F</color>
<color name="midderBlue">#262630</color>
<color name="lightBlue">#2C2C37</color>
<color name="invertedBackground">#FFFFFF</color>
<color name="invertedTextColor">#000000</color>
<color name="invertedIconTint">#000000</color>
<color name="transparent">#00000000</color>
</resources>

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<item name="skipButtonFitness" type="id" />
</resources>

View File

@@ -4,8 +4,10 @@
<string name="navigation_drawer_open">Open navigation drawer</string>
<string name="navigation_drawer_close">Open navigation close</string>
<string name="welkom_bij_fitbot">Welkom bij FitBot</string>
<string name="robot_helpt">de robot die helpt om fit te blijven</string>
<string name="start">Start</string>
<string name="help">Help</string>
<string name="todo">TODO</string>
@@ -14,7 +16,16 @@
<string name="skip">Skip</string>
<string name="complete">Complete</string>
<string name="uitleg">Als je op de startknop drukt komen oefingen op het scherm. Het doel is om die zo goedmogelijk na te doen zodat je punten verzameld. Als je klaar bent kunt u op de COMPLETE knop drukken in het sport scherm en dan kunt u uw punten inzien</string>
<color name="red">#f22b1d</color>
<string name="uitlegStart">Druk op Start om de oefening te beginnen</string>
<string name="uitlegHome">Ga terug naar het begin scherm door op het huisje te klikken</string>
<string name="afgerond">Oefeningen afgerond</string>
<string name="voltooid">U heeft de oefeningen voltooid! \n Druk op start om nog een sessie te beginnen</string>
<string name="score">Score:</string>
<string name="title">Title</string>
<string name="context">ContextContextContext</string>
<string name="description">Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry\'s standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.</string>
<string name="descriptionTitle">Description</string>
</resources>

View File

@@ -2,8 +2,11 @@
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
<item name="android:colorBackground">@color/darkBlue</item>
</style>
<style name="ToolbarNav" parent="Theme.AppCompat.Light.NoActionBar">
<item name="android:textColorSecondary">@color/white</item>
</style>
<style name="ButtonStyle">
@@ -16,10 +19,17 @@
</style>
<style name="TextStyle">
<item name="android:textSize">36sp</item>
<item name="android:textColor">#000000</item>
<item name="android:textSize">25sp</item>
<item name="android:textColor">#FFFFFF</item>
<item name= "android:textStyle">bold</item>
<item name="android:padding">6dp</item>
</style>
<style name="TextStyleTitle">
<item name="android:textSize">40sp</item>
<item name="android:textColor">#D3D3D3</item>
<item name= "android:textStyle">bold</item>
<item name="android:padding">6dp</item>
</style>
</resources>

View File

@@ -0,0 +1,28 @@
package com.example.fitbot;
import com.example.fitbot.exercise.Exercise;
import com.example.fitbot.exercise.ExerciseManager;
import org.junit.Test;
public class DatabaseFetchingTest {
@Test
public void testDatabaseFetching() {
Exercise exercise = ExerciseManager.fetchExerciseFromDatabase();
assert exercise != null;
System.out.println("\n---------------------------------");
System.out.println("Exercise:");
System.out.println("Name: " + exercise.title);
System.out.println("Description: " + exercise.description);
System.out.println("Muscle Group: " + exercise.muscleGroup);
System.out.println("Image URL: " + exercise.imageUrl);
System.out.println("Video URL: " + exercise.videoUrl);
System.out.println("Exercise Time: " + exercise.exerciseTimeInSeconds);
System.out.println("\n---------------------------------");
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 150 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -0,0 +1,10 @@
# what did we do
We went on a walk with the members of the Buurtcampus-oost. They host these walks weekly and this on 28 may was their anniversary edition so we did a different route than usual. These walks were originally hosted by the AUAS (HvA). There were elderly people that went on these walks. They first gather at the OBA at linnaeusstraat, and normally they go on a walk around the neighborhood. However, since this was the anniversary edition, we had a different route and activity. We still went on a walk, however we first went with the bus to Amsterdam north, and then we walked to the Landmarkt. This is a market that also has a restaurant, and we had a drink there. We had plenty of time to get to know the elderly people that go on these walks and the students that do projects with these people. We did not want to make these people feel used, so we did not directly ask them questions about our project. However, we did talk and observe them.
# The findings
While walking to the bus stop, we already saw that these people are happy to move and want to get active. That's why they want to go on these walks together because it is a social way to move. While talking to some of them, it seemed like they want to have something extra that helps them get active. When talking to one of the students, we were talking about our projects, and then the student mentioned to us that they were making a closet with games in it. These games vary from active to board games. Since there were active games there such as ping pong, we asked the student if they would think our project would fit next to the closet as an extra tool. They sounded very excited and told us it would be lovely since it is a unique way to move. Since these elderly people really liked to be social and pepper is a social assertive robot.
These elderly people also seemed very pleased with the latest technology and are not scared of it. We found out they are using all kinds of new and niche technologies, such as apple watches. They also have grandchildren that talk about AI, and they found it very interesting and want to learn more about, however they are scared that it will make people lazy, and some jobs will cease to exist.
# conclusion
We are glad to see that are projects helps the walking group of the buurtcampus oost. We hope that we can but fitbot next to the closet with all the games and that some of them use it when they are waiting on other people to arrive. These people really seemed to be wanting to get more active but not really to know how to do it, and we are glad to assist them with the use of fitbot.

Binary file not shown.

After

Width:  |  Height:  |  Size: 196 KiB

View File

@@ -0,0 +1,62 @@
# Infrastructure UML
``` mermaid
classDiagram
Raspberry pi --> NodeJS
Raspberry pi --> Database
NodeJS --> Androidapp : getExerciseData (Wifi, Rest API)
Database <--> NodeJS : Database queries
ESP8266 --> Androidapp : getRotationalData (Wifi)
namespace Server {
class Raspberry pi {
+MariaDB
+Apache2
+NodeJS
Database()
Webserver()
}
class Database {
+ExerciseID
+ExerciseName
+ExerciseDescription
+ExerciseVideo
+GyroCoordinates
+MuscleGroup
}
class NodeJS {
+MariaDB
GetRandomExercise()
}
}
namespace Pepper {
class Androidapp {
+Java
+Android SDK
+QiSDK
motionProcessing()
robotMovement()
showVideo()
fitnessCycle()
}
}
namespace Hardware {
class ESP8266{
+RotationalX
+RotationalY
+RotationalZ
Gyroscope()
}
}
```

Some files were not shown because too many files have changed in this diff Show More