diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml
new file mode 100644
index 000000000..1562f1318
--- /dev/null
+++ b/.github/workflows/python.yml
@@ -0,0 +1,35 @@
+# This workflow will check the python code using black
+
+name: Python style check
+
+on:
+ push:
+ branches: [ master ]
+ paths:
+ - 'controller/**'
+ - 'policy/**'
+ - '.github/workflows/python.yml'
+ pull_request:
+ branches: [ master ]
+ paths:
+ - 'controller/**'
+ - 'policy/**'
+ - '.github/workflows/python.yml'
+
+jobs:
+ style-check:
+ name: Python style check
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v2
+ - name: Set up python
+ uses: actions/setup-python@v2
+ with:
+ python-version: 3.7
+ - name: Install dependencies
+ run: |
+ python -m pip install 'black[jupyter]'
+ - name: Check style
+ run: |
+ black --check .
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 3949bdeba..2f0310600 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -10,41 +10,74 @@
1. Submit an issue describing the changes you want to implement. If it's only minor changes/bug-fixes, you can skip to step 3.
2. After the scope was discussed in the issue, assign it to yourself. It should show up in the "To do" column in the OpenBot project.
3. Fork the project and clone it locally:
-
+
`git clone https://github.com//OpenBot.git`
4. Create a branch:
- `git checkout -b `
-
+ `git checkout -b `
+
where `` concisely describes the scope of the work.
-
+
5. Do the work, write good commit messages, push your branch to the forked repository:
-
- ```
+
+ ```bash
git add
git commit -m
git push --set-upstream origin
```
-
+
6. Create a [pull request](https://github.com/intel-isl/OpenBot/pulls) in GitHub and link the issue to it. It should show up in the "In progress" column in the OpenBot project.
7. Work on any code review feedback you may receive and push it to your fork. The pull request gets updated automatically.
8. Get a cold drink of your choice to reward yourself for making the world a better place.
## Guidelines
-- Use same style and formatting as rest of code.
- - For the Android code you can run:
- 1. `./gradlew checkStyle` --> returns java files with incorrect style.
- 2. `./gradlew applyStyle` --> applies neccessary style changes to all java files.
- - For the Arduino and Python code, just try to blend in.
+- Use same style and formatting as rest of code.
+ - For the Java (Android) and Python code see [below](#Formatting).
+ - For any other code, just try to blend in.
- Update documentation associated with code changes you made.
-- If you want to include 3rd party dependencies, please discuss this in the issue first.
+- If you want to include 3rd party dependencies, please discuss this in the issue first.
- Pull requests should implement single features with as few changes as possible.
- Make sure you don't include temporary or binary files (the gitignores should mostly take care of this).
- Rebase/merge master into your branch before you submit the pull request.
- If possible, test your code on Windows, Linux and OSX.
+## Formatting
+
+### Java
+
+We use a gradle script for formatting java code. Make sure you are in the `android` directory.
+
+You can check your code with:
+
+```bash
+./gradlew checkStyle
+```
+
+You can apply the style to all files by running:
+
+```bash
+./gradlew applyStyle
+```
+
+### Python
+
+We use [black](https://pypi.org/project/black/) for formatting python code.
+
+You can check your code in the current directory with:
+
+```bash
+black --check .
+```
+
+You can apply the style to all files in the current directory by running:
+
+```bash
+black .
+```
+
+## Further resources
If you are looking for more information about contributing to open-source projects, here are two good references:
diff --git a/README.md b/README.md
index 73517488b..71fce26e8 100644
--- a/README.md
+++ b/README.md
@@ -32,7 +32,7 @@ OpenBot leverages smartphones as brains for low-cost robots. We have designed a
- Build your own [Robot Body](body/README.md)
- Flash the [Arduino Firmware](firmware/README.md)
- Install the [Android Apps](android/README.md)
-- Control the robot via [Python](python/README.md)
+- Try the [Python Controller](controller/README.md)
- Train your own [Driving Policy](policy/README.md)
## Get the source code
diff --git a/android/app/src/main/java/org/openbot/autopilot/AutopilotFragment.java b/android/app/src/main/java/org/openbot/autopilot/AutopilotFragment.java
index f2f14f76d..ec08abf33 100644
--- a/android/app/src/main/java/org/openbot/autopilot/AutopilotFragment.java
+++ b/android/app/src/main/java/org/openbot/autopilot/AutopilotFragment.java
@@ -440,8 +440,11 @@ protected void handleDriveCommand(Control control) {
vehicle.setControl(control);
float left = vehicle.getLeftSpeed();
float right = vehicle.getRightSpeed();
- binding.controllerContainer.controlInfo.setText(
- String.format(Locale.US, "%.0f,%.0f", left, right));
+ requireActivity()
+ .runOnUiThread(
+ () ->
+ binding.controllerContainer.controlInfo.setText(
+ String.format(Locale.US, "%.0f,%.0f", left, right)));
}
@Override
diff --git a/android/app/src/main/java/org/openbot/common/ControlsFragment.java b/android/app/src/main/java/org/openbot/common/ControlsFragment.java
index 27c7584d5..8aaf50a98 100644
--- a/android/app/src/main/java/org/openbot/common/ControlsFragment.java
+++ b/android/app/src/main/java/org/openbot/common/ControlsFragment.java
@@ -168,6 +168,7 @@ private void handlePhoneControllerEvents() {
switch (commandType) {
case Constants.CMD_DRIVE:
JSONObject driveValue = event.getJSONObject("driveCmd");
+
vehicle.setControl(
new Control(
Float.parseFloat(driveValue.getString("l")),
diff --git a/android/app/src/main/java/org/openbot/env/ControllerConfig.java b/android/app/src/main/java/org/openbot/env/ControllerConfig.java
new file mode 100644
index 000000000..3dd1d83af
--- /dev/null
+++ b/android/app/src/main/java/org/openbot/env/ControllerConfig.java
@@ -0,0 +1,124 @@
+package org.openbot.env;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.util.Log;
+import androidx.preference.PreferenceManager;
+import org.openbot.utils.ConnectionUtils;
+
+public class ControllerConfig {
+ private static ControllerConfig _controllerConfig;
+ SharedPreferences preferences;
+
+ enum VIDEO_SERVER_TYPE {
+ WEBRTC,
+ RTSP
+ }
+
+ private final boolean isMirrored = true;
+ private final boolean mute = true;
+
+ private String currentServerType;
+
+ public static ControllerConfig getInstance() {
+ if (_controllerConfig == null) {
+ synchronized (PhoneController.class) {
+ if (_controllerConfig == null) _controllerConfig = new ControllerConfig();
+ }
+ }
+
+ return _controllerConfig;
+ }
+
+ void init(Context context) {
+ preferences = PreferenceManager.getDefaultSharedPreferences(context);
+ currentServerType = get("video_server", "WEBRTC");
+ monitorSettingUpdates();
+ }
+
+ private void set(String name, String value) {
+ SharedPreferences.Editor editor = preferences.edit();
+ editor.putString(name, value);
+ editor.apply();
+ }
+
+ private String get(String name, String defaultValue) {
+ try {
+ return preferences.getString(name, defaultValue);
+ } catch (ClassCastException e) {
+ return defaultValue;
+ }
+ }
+
+ private Boolean getBoolean(String name, Boolean defaultValue) {
+ try {
+ return preferences.getBoolean(name, defaultValue);
+ } catch (ClassCastException e) {
+ return defaultValue;
+ }
+ }
+
+ private void setBoolean(String name, boolean value) {
+ SharedPreferences.Editor editor = preferences.edit();
+ editor.putBoolean(name, value);
+ editor.apply();
+ }
+
+ // specific settings
+ public boolean isMirrored() {
+ return getBoolean("MIRRORED", true);
+ }
+
+ public void setMirrored(boolean mirrored) {
+ setBoolean("MIRRORED", mirrored);
+ }
+
+ public boolean isMute() {
+ return getBoolean("MUTE", true);
+ }
+
+ public void setMute(boolean mute) {
+ setBoolean("MUTE", mute);
+ }
+
+ public String getVideoServerType() {
+ return get("video_server", "WEBRTC");
+ }
+
+ public void setVideoServerType(String type) {
+ set("video_server", type);
+ }
+
+ private void monitorSettingUpdates() {
+ ControllerToBotEventBus.subscribe(
+ this.getClass().getSimpleName(),
+ event -> {
+ String commandType = event.getString("command");
+
+ switch (commandType) {
+ case "TOGGLE_MIRROR":
+ setMirrored(!isMirrored());
+
+ // inform the controller of current state
+ BotToControllerEventBus.emitEvent(
+ ConnectionUtils.createStatus("TOGGLE_MIRROR", isMirrored()));
+ break;
+
+ case "TOGGLE_SOUND":
+ setMute(!isMute());
+
+ // inform the controller of current state
+ BotToControllerEventBus.emitEvent(
+ ConnectionUtils.createStatus("TOGGLE_SOUND", isMute()));
+ break;
+ }
+ },
+ error -> {
+ Log.d(null, "Error occurred in monitorConnection: " + error);
+ },
+ event ->
+ event.has("command")
+ && (event.getString("command").contains("TOGGLE_MIRROR")
+ || event.getString("command").contains("TOGGLE_SOUND")));
+ }
+}
diff --git a/android/app/src/main/java/org/openbot/env/ControllerToBotEventBus.java b/android/app/src/main/java/org/openbot/env/ControllerToBotEventBus.java
index 555a5f7c5..e7a8fc2e5 100644
--- a/android/app/src/main/java/org/openbot/env/ControllerToBotEventBus.java
+++ b/android/app/src/main/java/org/openbot/env/ControllerToBotEventBus.java
@@ -7,6 +7,7 @@
import io.reactivex.rxjava3.subjects.PublishSubject;
import java.util.HashMap;
import java.util.Map;
+import org.json.JSONException;
import org.json.JSONObject;
public final class ControllerToBotEventBus {
@@ -17,7 +18,15 @@ private ControllerToBotEventBus() {}
private static final PublishSubject subject = PublishSubject.create();
- public static void emitEvent(JSONObject event) {
+ public static void emitEvent(String event) {
+ try {
+ emitEvent(new JSONObject(event));
+ } catch (JSONException e) {
+ e.printStackTrace();
+ }
+ }
+
+ private static void emitEvent(JSONObject event) {
subject.onNext(event);
}
diff --git a/android/app/src/main/java/org/openbot/env/ImageUtils.java b/android/app/src/main/java/org/openbot/env/ImageUtils.java
index eac0e06b5..5dfa3a470 100755
--- a/android/app/src/main/java/org/openbot/env/ImageUtils.java
+++ b/android/app/src/main/java/org/openbot/env/ImageUtils.java
@@ -21,6 +21,7 @@
import android.graphics.Bitmap;
import android.graphics.Matrix;
import android.graphics.RectF;
+import android.os.Build;
import android.view.Surface;
import java.io.File;
import java.io.FileOutputStream;
@@ -227,7 +228,11 @@ public static Matrix getTransformationMatrix(
}
public static int getScreenOrientation(Activity activity) {
- switch (activity.getDisplay().getRotation()) {
+ int rotation =
+ Build.VERSION.SDK_INT >= Build.VERSION_CODES.R
+ ? activity.getDisplay().getRotation()
+ : activity.getWindowManager().getDefaultDisplay().getRotation();
+ switch (rotation) {
case Surface.ROTATION_270:
return 270;
case Surface.ROTATION_180:
diff --git a/android/app/src/main/java/org/openbot/env/NearbyConnection.java b/android/app/src/main/java/org/openbot/env/NearbyConnection.java
index 6d90abd79..96ae8b323 100755
--- a/android/app/src/main/java/org/openbot/env/NearbyConnection.java
+++ b/android/app/src/main/java/org/openbot/env/NearbyConnection.java
@@ -37,8 +37,6 @@
import java.util.Timer;
import java.util.TimerTask;
import org.jetbrains.annotations.NotNull;
-import org.json.JSONException;
-import org.json.JSONObject;
import org.openbot.OpenBotApplication;
import org.openbot.utils.ConnectionUtils;
import timber.log.Timber;
@@ -156,11 +154,7 @@ public void onConnectionResult(@NonNull String endpointId, ConnectionResolution
pairedDeviceEndpointId = endpointId;
isConnected = true;
- try {
- ControllerToBotEventBus.emitEvent(new JSONObject("{command: \"CONNECTED\"}"));
- } catch (JSONException e) {
- e.printStackTrace();
- }
+ ControllerToBotEventBus.emitEvent("{command: \"CONNECTED\"}");
} else {
Timber.i("onConnectionResult: connection failed");
isConnected = false;
@@ -181,11 +175,7 @@ public void onDisconnected(@NonNull String endpointId) {
Toast.LENGTH_LONG)
.show();
Timber.i("onDisconnected: disconnected from the opponent");
- try {
- ControllerToBotEventBus.emitEvent(new JSONObject("{command: \"DISCONNECTED\"}"));
- } catch (JSONException e) {
- e.printStackTrace();
- }
+ ControllerToBotEventBus.emitEvent("{command: \"DISCONNECTED\"}");
}
};
diff --git a/android/app/src/main/java/org/openbot/env/NetworkServiceConnection.java b/android/app/src/main/java/org/openbot/env/NetworkServiceConnection.java
index 91207195f..b168ab46f 100644
--- a/android/app/src/main/java/org/openbot/env/NetworkServiceConnection.java
+++ b/android/app/src/main/java/org/openbot/env/NetworkServiceConnection.java
@@ -18,8 +18,6 @@
import java.util.Scanner;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
-import org.json.JSONException;
-import org.json.JSONObject;
import org.openbot.utils.ConnectionUtils;
import timber.log.Timber;
@@ -149,12 +147,7 @@ public void onServiceLost(NsdServiceInfo service) {
((Activity) context)
.runOnUiThread(
() -> {
- try {
- ControllerToBotEventBus.emitEvent(
- new JSONObject("{command: \"DISCONNECTED\"}"));
- } catch (JSONException e) {
- e.printStackTrace();
- }
+ ControllerToBotEventBus.emitEvent("{command: \"DISCONNECTED\"}");
});
}
@@ -209,11 +202,7 @@ public void onServiceResolved(NsdServiceInfo serviceInfo) {
((Activity) context)
.runOnUiThread(
() -> {
- try {
- ControllerToBotEventBus.emitEvent(new JSONObject("{command: \"CONNECTED\"}"));
- } catch (JSONException e) {
- e.printStackTrace();
- }
+ ControllerToBotEventBus.emitEvent("{command: \"CONNECTED\"}");
});
new Thread("Receiver Thread") {
@@ -340,12 +329,7 @@ void close() {
((Activity) context)
.runOnUiThread(
() -> {
- try {
- ControllerToBotEventBus.emitEvent(
- new JSONObject("{command: \"DISCONNECTED\"}"));
- } catch (JSONException e) {
- e.printStackTrace();
- }
+ ControllerToBotEventBus.emitEvent("{command: \"DISCONNECTED\"}");
});
} catch (IOException e) {
e.printStackTrace();
diff --git a/android/app/src/main/java/org/openbot/env/PhoneController.java b/android/app/src/main/java/org/openbot/env/PhoneController.java
index 8b75260d3..e749ef31b 100644
--- a/android/app/src/main/java/org/openbot/env/PhoneController.java
+++ b/android/app/src/main/java/org/openbot/env/PhoneController.java
@@ -2,13 +2,10 @@
import android.app.Activity;
import android.content.Context;
-import android.content.SharedPreferences;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
-import androidx.preference.PreferenceManager;
-import org.json.JSONException;
import org.json.JSONObject;
import org.openbot.R;
import org.openbot.customview.AutoFitSurfaceGlView;
@@ -23,12 +20,6 @@ public class PhoneController {
private ConnectionSelector connectionSelector;
private IVideoServer videoServer;
private View view = null;
- private Config config = new Config();
-
- private enum VIDEO_SERVER_TYPE {
- WEBRTC,
- RTSP
- };
public static PhoneController getInstance(Context context) {
if (_phoneController == null) { // Check for the first time
@@ -40,65 +31,24 @@ public static PhoneController getInstance(Context context) {
}
}
- // The function bellow will attempt to switch server types if another is selected in settings.
- // However, this is not reliable at the moment so it is not used here.
- // The user can change the setting once when the app is started, then select FreeRoam fragment.
- // Future changes will not be detected until app is restarted..
-
- // _phoneController.config.checkForConfigChange(context);
-
return _phoneController;
}
- private class Config {
- VIDEO_SERVER_TYPE currentServerType;
-
- void init(Context context) {
- SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
- currentServerType =
- preferences.getString("video_server", "WebRTC").equals("RTSP")
- ? VIDEO_SERVER_TYPE.RTSP
- : VIDEO_SERVER_TYPE.WEBRTC;
- }
-
- private void checkForConfigChange(Context context) {
- SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
- VIDEO_SERVER_TYPE serverType =
- preferences.getString("video_server", "WebRTC").equals("RTSP")
- ? VIDEO_SERVER_TYPE.RTSP
- : VIDEO_SERVER_TYPE.WEBRTC;
- if (serverType != currentServerType) {
- currentServerType = serverType;
- if (videoServer != null) {
- videoServer.setCanStart(false); // stop old server
- }
- // we have a new video server, we must re-init the Phone Controller so the server will start
- // properly.
- PhoneController.this.init(context);
- }
- }
- }
-
class DataReceived implements IDataReceived {
@Override
public void dataReceived(String commandStr) {
- try {
- ControllerToBotEventBus.emitEvent(new JSONObject(commandStr));
- } catch (JSONException e) {
- e.printStackTrace();
- }
+ ControllerToBotEventBus.emitEvent(commandStr);
}
}
private void init(Context context) {
- config.init(context);
-
- SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
- VIDEO_SERVER_TYPE serverType =
- preferences.getString("video_server", "WebRTC").equals("RTSP")
- ? VIDEO_SERVER_TYPE.RTSP
- : VIDEO_SERVER_TYPE.WEBRTC;
- videoServer = serverType == VIDEO_SERVER_TYPE.RTSP ? new RtspServer() : new WebRtcServer();
+ ControllerConfig.getInstance().init(context);
+
+ videoServer =
+ "RTSP".equals(ControllerConfig.getInstance().getVideoServerType())
+ ? new RtspServer()
+ : new WebRtcServer();
+
videoServer.init(context);
videoServer.setCanStart(true);
diff --git a/android/app/src/main/java/org/openbot/env/RtspServer.java b/android/app/src/main/java/org/openbot/env/RtspServer.java
index 17dbca9ac..8ee83d11f 100644
--- a/android/app/src/main/java/org/openbot/env/RtspServer.java
+++ b/android/app/src/main/java/org/openbot/env/RtspServer.java
@@ -34,7 +34,6 @@ public class RtspServer
private final String TAG = "RtspServerPedroOpenGL";
private RtspServerCamera1 rtspServerCamera1;
private View view;
- private final MirrorImageSetter mirror = new MirrorImageSetter();
private AndGate andGate;
private Context context;
private Size resolution = new Size(640, 360);
@@ -184,7 +183,10 @@ private void startServer(Size resolution, int port) {
resolution.getWidth(), resolution.getHeight(), 20, 1200 * 1024, 2, 0)) {
rtspServerCamera1.startStream("");
- cameraControlHandler.disableAudio();
+ boolean isMute = ControllerConfig.getInstance().isMute();
+ if (isMute) {
+ cameraControlHandler.disableAudio();
+ }
// Delay starting the client for a second to make sure the server is started.
Runnable action = () -> startClient();
@@ -199,18 +201,6 @@ public void setResolution(int w, int h) {
andGate.set("resolution set", true);
}
- class MirrorImageSetter {
- public boolean isMirrored() {
- return isMirrored;
- }
-
- public void setMirrored(boolean mirrored) {
- isMirrored = mirrored;
- }
-
- private boolean isMirrored = true;
- }
-
// ConnectCheckerRtsp callbacks
@Override
public void onConnectionSuccessRtsp() {
@@ -291,34 +281,14 @@ private void disableAudio() {
}
private void handleCameraControlEvents() {
+ ControllerConfig config = ControllerConfig.getInstance();
+
ControllerToBotEventBus.subscribe(
this.getClass().getSimpleName(),
event -> {
String commandType = event.getString("command");
-
- switch (commandType) {
- case "TOGGLE_SOUND":
- Log.i(TAG, "TOGGLE_SOUND");
-
- if (rtspServerCamera1.isAudioMuted()) rtspServerCamera1.enableAudio();
- else rtspServerCamera1.disableAudio();
-
- // inform the controller of current state
- BotToControllerEventBus.emitEvent(
- ConnectionUtils.createStatus(
- "TOGGLE_SOUND", !rtspServerCamera1.isAudioMuted()));
- break;
-
- case "TOGGLE_MIRROR":
- Log.i(TAG, "TOGGLE_MIRROR");
-
- mirror.setMirrored(!mirror.isMirrored());
-
- // inform the controller of current state
- BotToControllerEventBus.emitEvent(
- ConnectionUtils.createStatus("TOGGLE_MIRROR", mirror.isMirrored()));
- break;
- }
+ if (rtspServerCamera1.isAudioMuted()) rtspServerCamera1.enableAudio();
+ else rtspServerCamera1.disableAudio();
},
error -> Log.d(null, "Error occurred in ControllerToBotEventBus: " + error),
event ->
diff --git a/android/app/src/main/java/org/openbot/env/SharedPreferencesManager.java b/android/app/src/main/java/org/openbot/env/SharedPreferencesManager.java
index 8d24b1350..a5fa724e1 100644
--- a/android/app/src/main/java/org/openbot/env/SharedPreferencesManager.java
+++ b/android/app/src/main/java/org/openbot/env/SharedPreferencesManager.java
@@ -32,6 +32,7 @@ public class SharedPreferencesManager {
private static final String NUM_THREAD = "NUM_THREAD";
private static final String CAMERA_SWITCH = "CAMERA_SWITCH";
private static final String SHEET_EXPANDED = "SHEET_EXPANDED";
+ private static final String DELAY = "DELAY";
private final SharedPreferences preferences;
@@ -145,4 +146,20 @@ public void setCameraSwitch(boolean isChecked) {
public void setSheetExpanded(boolean expanded) {
preferences.edit().putBoolean(SHEET_EXPANDED, expanded).apply();
}
+
+ public void setSensorStatus(boolean status, String sensor) {
+ preferences.edit().putBoolean(sensor, status).apply();
+ }
+
+ public boolean getSensorStatus(String sensor) {
+ return preferences.getBoolean(sensor, false);
+ }
+
+ public void setDelay(int delay) {
+ preferences.edit().putInt(DELAY, delay).apply();
+ }
+
+ public int getDelay() {
+ return preferences.getInt(DELAY, 200);
+ }
}
diff --git a/android/app/src/main/java/org/openbot/env/WebRtcServer.java b/android/app/src/main/java/org/openbot/env/WebRtcServer.java
index 74e864f04..e3d6427e0 100644
--- a/android/app/src/main/java/org/openbot/env/WebRtcServer.java
+++ b/android/app/src/main/java/org/openbot/env/WebRtcServer.java
@@ -61,7 +61,6 @@ public class WebRtcServer implements IVideoServer {
private final String TAG = "WebRtcPeer";
private SurfaceViewRenderer view;
private Size resolution = new Size(640, 360);
- private MirrorImageSetter mirror = new MirrorImageSetter();
public static final String VIDEO_TRACK_ID = "ARDAMSv0";
public static final int VIDEO_RESOLUTION_WIDTH = 640;
@@ -205,7 +204,10 @@ private void startStreamingVideo() {
mediaStream.addTrack(localAudioTrack);
peerConnection.addStream(mediaStream);
- cameraControlHandler.disableAudio();
+ boolean isMute = ControllerConfig.getInstance().isMute();
+ if (isMute) {
+ cameraControlHandler.disableAudio();
+ }
}
private void stopStreamingVideo() {
@@ -446,43 +448,19 @@ private void disableAudio() {
}
private void handleCameraControlEvents() {
+ ControllerConfig config = ControllerConfig.getInstance();
ControllerToBotEventBus.subscribe(
this.getClass().getSimpleName(),
event -> {
String commandType = event.getString("command");
- switch (commandType) {
- case "TOGGLE_SOUND":
- if (mediaStream.audioTracks.size() > 0) {
- AudioTrack audio = mediaStream.audioTracks.get(0);
- audio.setEnabled(!audio.enabled());
-
- // inform the controller of current state
- BotToControllerEventBus.emitEvent(
- ConnectionUtils.createStatus("TOGGLE_SOUND", audio.enabled()));
- }
-
- break;
-
- case "TOGGLE_MIRROR":
- mirror.setMirrored(!mirror.isMirrored());
-
- // inform the controller of current state
- BotToControllerEventBus.emitEvent(
- ConnectionUtils.createStatus("TOGGLE_MIRROR", mirror.isMirrored()));
- break;
+ if (mediaStream.audioTracks.size() > 0) {
+ AudioTrack audio = mediaStream.audioTracks.get(0);
+ audio.setEnabled(!audio.enabled());
}
},
error -> Log.d(null, "Error occurred in ControllerToBotEventBus: " + error),
- event ->
- event.has("command")
- && ("TOGGLE_SOUND".equals(event.getString("command"))
- || "TOGGLE_MIRROR"
- .equals(
- event.getString(
- "command"))) // filter out all but the "TOGGLE_SOUND" and
- // "TOGGLE_MIRROR" commands..
- );
+ event -> event.has("command") && "TOGGLE_SOUND".equals(event.getString("command")));
}
}
@@ -492,18 +470,6 @@ private void beep() {
tg.startTone(ToneGenerator.TONE_CDMA_ALERT_NETWORK_LITE);
}
- class MirrorImageSetter {
- public boolean isMirrored() {
- return isMirrored;
- }
-
- public void setMirrored(boolean mirrored) {
- isMirrored = mirrored;
- }
-
- private boolean isMirrored = true;
- }
-
class SignalingHandler {
void handleControllerWebRtcEvents() {
ControllerToBotEventBus.subscribe(
diff --git a/android/app/src/main/java/org/openbot/logging/LoggerFragment.java b/android/app/src/main/java/org/openbot/logging/LoggerFragment.java
index 8932ec0dc..f56d9790b 100644
--- a/android/app/src/main/java/org/openbot/logging/LoggerFragment.java
+++ b/android/app/src/main/java/org/openbot/logging/LoggerFragment.java
@@ -87,6 +87,11 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat
setControlMode(Enums.ControlMode.getByID(preferencesManager.getControlMode()));
setDriveMode(Enums.DriveMode.getByID(preferencesManager.getDriveMode()));
+ binding.sensorDataButton.setOnClickListener(
+ v -> {
+ SensorsDialog sensorsDialog = new SensorsDialog();
+ sensorsDialog.show(getChildFragmentManager(), sensorsDialog.getTag());
+ });
binding.controllerContainer.controlMode.setOnClickListener(
v -> {
Enums.ControlMode controlMode =
diff --git a/android/app/src/main/java/org/openbot/logging/SensorListAdapter.java b/android/app/src/main/java/org/openbot/logging/SensorListAdapter.java
new file mode 100644
index 000000000..989c27179
--- /dev/null
+++ b/android/app/src/main/java/org/openbot/logging/SensorListAdapter.java
@@ -0,0 +1,55 @@
+package org.openbot.logging;
+
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+import android.widget.CheckBox;
+import androidx.recyclerview.widget.RecyclerView;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import org.jetbrains.annotations.NotNull;
+import org.openbot.databinding.ItemSensorBinding;
+import org.openbot.env.SharedPreferencesManager;
+
+public class SensorListAdapter extends RecyclerView.Adapter {
+
+ private final List names;
+ private final List status;
+ private final SharedPreferencesManager preferencesManager;
+
+ public SensorListAdapter(
+ HashMap items, SharedPreferencesManager preferencesManager) {
+ names = new ArrayList<>(items.keySet());
+ status = new ArrayList<>(items.values());
+ this.preferencesManager = preferencesManager;
+ }
+
+ @NotNull
+ @Override
+ public ViewHolder onCreateViewHolder(@NotNull ViewGroup parent, int viewType) {
+ return new ViewHolder(
+ ItemSensorBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false));
+ }
+
+ @Override
+ public void onBindViewHolder(final ViewHolder holder, int position) {
+ holder.checkBox.setText(names.get(position));
+ holder.checkBox.setChecked(status.get(position));
+ holder.checkBox.setOnClickListener(
+ v -> preferencesManager.setSensorStatus(holder.checkBox.isChecked(), names.get(position)));
+ }
+
+ @Override
+ public int getItemCount() {
+ return names.size();
+ }
+
+ public static class ViewHolder extends RecyclerView.ViewHolder {
+ public CheckBox checkBox;
+
+ public ViewHolder(ItemSensorBinding binding) {
+ super(binding.getRoot());
+ checkBox = binding.check;
+ }
+ }
+}
diff --git a/android/app/src/main/java/org/openbot/logging/SensorService.java b/android/app/src/main/java/org/openbot/logging/SensorService.java
index f3c29f40f..2c2a714cf 100644
--- a/android/app/src/main/java/org/openbot/logging/SensorService.java
+++ b/android/app/src/main/java/org/openbot/logging/SensorService.java
@@ -10,12 +10,14 @@
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.location.Location;
+import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.os.SystemClock;
+import androidx.annotation.RequiresApi;
import com.google.android.gms.location.FusedLocationProviderClient;
import com.google.android.gms.location.LocationCallback;
import com.google.android.gms.location.LocationRequest;
@@ -27,6 +29,8 @@
import java.io.IOException;
import org.openbot.R;
import org.openbot.env.Logger;
+import org.openbot.env.SharedPreferencesManager;
+import org.openbot.utils.Enums;
public class SensorService extends Service implements SensorEventListener {
private SensorManager sensorManager;
@@ -37,9 +41,9 @@ public class SensorService extends Service implements SensorEventListener {
private Sensor lightSensor;
private Sensor proximitySensor;
private Sensor pressureSensor;
+ private Sensor temperatureSensor;
private Sensor poseSensor;
private Sensor motionSensor;
- private Sensor stationarySensor;
private BufferedWriter accelerometerLog;
private BufferedWriter gyroscopeLog;
@@ -48,6 +52,7 @@ public class SensorService extends Service implements SensorEventListener {
private BufferedWriter lightLog;
private BufferedWriter proximityLog;
private BufferedWriter pressureLog;
+ private BufferedWriter temperatureLog;
private BufferedWriter poseLog;
private BufferedWriter motionLog;
private BufferedWriter gpsLog;
@@ -72,6 +77,9 @@ public class SensorService extends Service implements SensorEventListener {
private static final Logger LOGGER = new Logger();
Messenger messenger = new Messenger(new SensorMessageHandler());
+ private SharedPreferencesManager preferencesManager;
+
+ @RequiresApi(api = Build.VERSION_CODES.N)
@Override
public final void onCreate() {
super.onCreate();
@@ -83,11 +91,12 @@ public final void onCreate() {
lightSensor = sensorManager.getDefaultSensor(Sensor.TYPE_LIGHT);
proximitySensor = sensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY);
pressureSensor = sensorManager.getDefaultSensor(Sensor.TYPE_PRESSURE);
+ temperatureSensor = sensorManager.getDefaultSensor(Sensor.TYPE_AMBIENT_TEMPERATURE);
poseSensor = sensorManager.getDefaultSensor(Sensor.TYPE_POSE_6DOF);
motionSensor = sensorManager.getDefaultSensor(Sensor.TYPE_MOTION_DETECT);
- stationarySensor = sensorManager.getDefaultSensor(Sensor.TYPE_STATIONARY_DETECT);
// Initialize the FusedLocationClient.
fusedLocationClient = LocationServices.getFusedLocationProviderClient(this);
+ preferencesManager = new SharedPreferencesManager(this);
}
@Override
@@ -103,44 +112,81 @@ public int onStartCommand(Intent intent, int flags, int startId) {
logFolder = (String) extras.get("logFolder");
}
- accelerometerLog = openLog(logFolder, "accelerometerLog.txt");
- // appendLog(mAccelerometerLog, mAccelerometer.getName());
- appendLog(accelerometerLog, "timestamp[ns],x[m/s^2],y[m/s^2],z[m/s^2]");
+ int delay = (int) (preferencesManager.getDelay() * 1000);
+ if (preferencesManager.getSensorStatus(Enums.SensorType.ACCELEROMETER.getSensor())
+ && accelerometerSensor != null) {
+ accelerometerLog = openLog(logFolder, "accelerometerLog.txt");
+ appendLog(accelerometerLog, "timestamp[ns],x[m/s^2],y[m/s^2],z[m/s^2]");
+ sensorManager.registerListener(this, accelerometerSensor, delay);
+ }
+
+ if (preferencesManager.getSensorStatus(Enums.SensorType.GYROSCOPE.getSensor())
+ && gyroscopeSensor != null) {
+ gyroscopeLog = openLog(logFolder, "gyroscopeLog.txt");
+ appendLog(gyroscopeLog, "timestamp[ns],x[rad/s],y[rad/s],z[rad/s]");
+ sensorManager.registerListener(this, gyroscopeSensor, delay);
+ }
- gyroscopeLog = openLog(logFolder, "gyroscopeLog.txt");
- // appendLog(mGyroscopeLog, mGyroscope.getName());
- appendLog(gyroscopeLog, "timestamp[ns],x[rad/s],y[rad/s],z[rad/s]");
+ if (preferencesManager.getSensorStatus(Enums.SensorType.GRAVITY.getSensor())
+ && gravitySensor != null) {
+ gravityLog = openLog(logFolder, "gravityLog.txt");
+ appendLog(gravityLog, "timestamp[ns],x[m/s^2],y[m/s^2],z[m/s^2]");
+ sensorManager.registerListener(this, gravitySensor, delay);
+ }
- gravityLog = openLog(logFolder, "gravityLog.txt");
- // appendLog(mGravityLog, mGravity.getName());
- appendLog(gravityLog, "timestamp[ns],x[m/s^2],y[m/s^2],z[m/s^2]");
+ if (preferencesManager.getSensorStatus(Enums.SensorType.MAGNETIC.getSensor())
+ && magneticSensor != null) {
+ magneticLog = openLog(logFolder, "magneticLog.txt");
+ appendLog(magneticLog, "timestamp[ns],x[uT],y[uT],z[uT]");
+ sensorManager.registerListener(this, magneticSensor, delay);
+ }
- magneticLog = openLog(logFolder, "magneticLog.txt");
- // appendLog(mMagneticLog, mMagnetic.getName());
- appendLog(magneticLog, "timestamp[ns],x[uT],y[uT],z[uT]");
+ if (preferencesManager.getSensorStatus(Enums.SensorType.LIGHT.getSensor())
+ && lightSensor != null) {
+ lightLog = openLog(logFolder, "lightLog.txt");
+ appendLog(lightLog, "timestamp[ns],light[lux]");
+ sensorManager.registerListener(this, lightSensor, delay);
+ }
- lightLog = openLog(logFolder, "lightLog.txt");
- // appendLog(mLightLog, mLight.getName());
- appendLog(lightLog, "timestamp[ns],light[lux]");
+ if (preferencesManager.getSensorStatus(Enums.SensorType.PROXIMITY.getSensor())
+ && proximitySensor != null) {
+ proximityLog = openLog(logFolder, "proximityLog.txt");
+ appendLog(proximityLog, "timestamp[ns],proximity[cm]");
+ sensorManager.registerListener(this, proximitySensor, delay);
+ }
- proximityLog = openLog(logFolder, "proximityLog.txt");
- // appendLog(mProximityLog, mProximity.getName());
- appendLog(proximityLog, "timestamp[ns],proximity[cm]");
+ if (preferencesManager.getSensorStatus(Enums.SensorType.PRESSURE.getSensor())
+ && pressureSensor != null) {
+ pressureLog = openLog(logFolder, "pressureLog.txt");
+ appendLog(pressureLog, "timestamp[ns],pressure[hPa]");
+ sensorManager.registerListener(this, pressureSensor, delay);
+ }
- pressureLog = openLog(logFolder, "pressureLog.txt");
- // appendLog(mPressureLog, mPressure.getName());
- appendLog(pressureLog, "timestamp[ns],pressure[hPa]");
+ if (preferencesManager.getSensorStatus(Enums.SensorType.TEMPERATURE.getSensor())
+ && temperatureSensor != null) {
+ temperatureLog = openLog(logFolder, "temperatureLog.txt");
+ appendLog(temperatureLog, "timestamp[ns],temperature[degrees]");
+ sensorManager.registerListener(this, temperatureSensor, delay);
+ }
- poseLog = openLog(logFolder, "poseLog.txt");
- // appendLog(mPoseLog, mPose.getName());
- appendLog(poseLog, "timestamp[ns],x,y,z,w,x,y,z,dx,dy,dz,dw,dx,dy,dz,id");
+ if (preferencesManager.getSensorStatus(Enums.SensorType.POSE.getSensor())
+ && poseSensor != null) {
+ poseLog = openLog(logFolder, "poseLog.txt");
+ appendLog(poseLog, "timestamp[ns],x,y,z,w,x,y,z,dx,dy,dz,dw,dx,dy,dz,id");
+ sensorManager.registerListener(this, poseSensor, delay);
+ }
- motionLog = openLog(logFolder, "motionLog.txt");
- // appendLog(mMotionLog, mMotion.getName());
- appendLog(motionLog, "timestamp[ns],motion");
+ if (preferencesManager.getSensorStatus(Enums.SensorType.MOTION.getSensor())
+ && motionSensor != null) {
+ motionLog = openLog(logFolder, "motionLog.txt");
+ appendLog(motionLog, "timestamp[ns],motion");
+ sensorManager.registerListener(this, motionSensor, delay);
+ }
- gpsLog = openLog(logFolder, "gpsLog.txt");
- appendLog(gpsLog, "timestamp[ns],latitude,longitude,altitude[m],bearing,speed[m/s]");
+ if (preferencesManager.getSensorStatus(Enums.SensorType.GPS.getSensor())) {
+ gpsLog = openLog(logFolder, "gpsLog.txt");
+ appendLog(gpsLog, "timestamp[ns],latitude,longitude,altitude[m],bearing,speed[m/s]");
+ }
frameLog = openLog(logFolder, "rgbFrames.txt");
appendLog(frameLog, "timestamp[ns],frame");
@@ -154,28 +200,10 @@ public int onStartCommand(Intent intent, int flags, int startId) {
indicatorLog = openLog(logFolder, "indicatorLog.txt");
appendLog(indicatorLog, "timestamp[ns],signal");
- vehicleLog = openLog(logFolder, "vehicleLog.txt");
- appendLog(vehicleLog, "timestamp[ns],batteryVoltage,leftWheel,rightWheel,obstacle");
-
- sensorManager.registerListener(this, accelerometerSensor, SensorManager.SENSOR_DELAY_NORMAL);
- sensorManager.registerListener(
- this, gyroscopeSensor, android.hardware.SensorManager.SENSOR_DELAY_NORMAL);
- sensorManager.registerListener(
- this, gravitySensor, android.hardware.SensorManager.SENSOR_DELAY_NORMAL);
- sensorManager.registerListener(
- this, magneticSensor, android.hardware.SensorManager.SENSOR_DELAY_NORMAL);
- sensorManager.registerListener(
- this, lightSensor, android.hardware.SensorManager.SENSOR_DELAY_NORMAL);
- sensorManager.registerListener(
- this, proximitySensor, android.hardware.SensorManager.SENSOR_DELAY_NORMAL);
- sensorManager.registerListener(
- this, pressureSensor, android.hardware.SensorManager.SENSOR_DELAY_NORMAL);
- sensorManager.registerListener(
- this, poseSensor, android.hardware.SensorManager.SENSOR_DELAY_NORMAL);
- sensorManager.registerListener(
- this, motionSensor, android.hardware.SensorManager.SENSOR_DELAY_NORMAL);
- sensorManager.registerListener(
- this, stationarySensor, android.hardware.SensorManager.SENSOR_DELAY_NORMAL);
+ if (preferencesManager.getSensorStatus(Enums.SensorType.VEHICLE.getSensor())) {
+ vehicleLog = openLog(logFolder, "vehicleLog.txt");
+ appendLog(vehicleLog, "timestamp[ns],batteryVoltage,leftWheel,rightWheel,obstacle");
+ }
locationCallback =
new LocationCallback() {
@@ -290,6 +318,10 @@ public final void onSensorChanged(SensorEvent event) {
// Atmospheric pressure in mPa (millibar)
appendLog(pressureLog, event.timestamp + "," + event.values[0]);
break;
+ case Sensor.TYPE_AMBIENT_TEMPERATURE:
+ // Ambient temperature in degrees
+ appendLog(temperatureLog, event.timestamp + "," + event.values[0]);
+ break;
case Sensor.TYPE_POSE_6DOF:
// values[0]: x*sin(θ/2)
// values[1]: y*sin(θ/2)
@@ -395,6 +427,7 @@ public void onDestroy() {
if (lightLog != null) closeLog(lightLog);
if (proximityLog != null) closeLog(proximityLog);
if (pressureLog != null) closeLog(pressureLog);
+ if (temperatureLog != null) closeLog(temperatureLog);
if (poseLog != null) closeLog(poseLog);
if (motionLog != null) closeLog(motionLog);
if (gpsLog != null) closeLog(gpsLog);
@@ -441,7 +474,7 @@ public void appendLog(BufferedWriter writer, String text) {
writer.append(text);
writer.newLine();
writer.flush();
- } catch (IOException e) {
+ } catch (Exception e) {
e.printStackTrace();
}
}
diff --git a/android/app/src/main/java/org/openbot/logging/SensorsDialog.java b/android/app/src/main/java/org/openbot/logging/SensorsDialog.java
new file mode 100644
index 000000000..38343a6ae
--- /dev/null
+++ b/android/app/src/main/java/org/openbot/logging/SensorsDialog.java
@@ -0,0 +1,83 @@
+package org.openbot.logging;
+
+import android.content.Context;
+import android.hardware.Sensor;
+import android.hardware.SensorManager;
+import android.os.Bundle;
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import androidx.fragment.app.DialogFragment;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import org.jetbrains.annotations.NotNull;
+import org.openbot.R;
+import org.openbot.databinding.DialogSensorsBinding;
+import org.openbot.env.SharedPreferencesManager;
+import org.openbot.utils.Enums;
+
+public class SensorsDialog extends DialogFragment {
+
+ public SensorsDialog() {}
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setStyle(DialogFragment.STYLE_NORMAL, R.style.FullScreenDialog);
+ }
+
+ @Override
+ public View onCreateView(
+ @NotNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ SharedPreferencesManager preferencesManager = new SharedPreferencesManager(requireContext());
+ DialogSensorsBinding binding = DialogSensorsBinding.inflate(LayoutInflater.from(getContext()));
+ SensorManager sensorManager =
+ (SensorManager) requireActivity().getSystemService(Context.SENSOR_SERVICE);
+ List sensorList = sensorManager.getSensorList(Sensor.TYPE_ALL);
+
+ HashMap list = new LinkedHashMap<>();
+ list.put(
+ Enums.SensorType.VEHICLE.getSensor(),
+ preferencesManager.getSensorStatus(Enums.SensorType.VEHICLE.getSensor()));
+
+ // Every modern phone has GPS
+ list.put(
+ Enums.SensorType.GPS.getSensor(),
+ preferencesManager.getSensorStatus(Enums.SensorType.GPS.getSensor()));
+
+ for (Sensor sensor : sensorList) {
+ for (Enums.SensorType name : Enums.SensorType.values())
+ if (sensor.getStringType().toLowerCase().contains(name.getSensor().toLowerCase())) {
+ list.put(name.getSensor(), preferencesManager.getSensorStatus(name.getSensor()));
+ break;
+ }
+ }
+
+ SensorListAdapter adapter = new SensorListAdapter(list, preferencesManager);
+
+ binding.listView.setLayoutManager(new LinearLayoutManager(requireContext()));
+ binding.listView.setAdapter(adapter);
+ binding.dismiss.setOnClickListener(v -> dismiss());
+
+ binding.delay.setText(String.valueOf(preferencesManager.getDelay()));
+ binding.delay.addTextChangedListener(
+ new TextWatcher() {
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
+
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before, int count) {}
+
+ @Override
+ public void afterTextChanged(Editable s) {
+ if (!s.toString().isEmpty())
+ preferencesManager.setDelay(Integer.parseInt(s.toString()));
+ }
+ });
+ return binding.getRoot();
+ }
+}
diff --git a/android/app/src/main/java/org/openbot/main/SettingsFragment.java b/android/app/src/main/java/org/openbot/main/SettingsFragment.java
index aa2985780..6ed322a7f 100644
--- a/android/app/src/main/java/org/openbot/main/SettingsFragment.java
+++ b/android/app/src/main/java/org/openbot/main/SettingsFragment.java
@@ -7,8 +7,10 @@
import android.app.Activity;
import android.content.Intent;
+import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle;
+import android.os.Handler;
import android.provider.Settings;
import android.view.View;
import android.widget.Toast;
@@ -16,7 +18,9 @@
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.appcompat.app.AlertDialog;
import androidx.lifecycle.ViewModelProvider;
+import androidx.preference.ListPreference;
import androidx.preference.PreferenceFragmentCompat;
import androidx.preference.SwitchPreferenceCompat;
import org.openbot.R;
@@ -37,41 +41,40 @@ public class SettingsFragment extends PreferenceFragmentCompat {
private final ActivityResultLauncher requestPermissionLauncher =
registerForActivityResult(
new ActivityResultContracts.RequestMultiplePermissions(),
- result -> {
- result.forEach(
- (permission, granted) -> {
- switch (permission) {
- case PERMISSION_CAMERA:
- if (granted) camera.setChecked(true);
- else {
- camera.setChecked(false);
- PermissionUtils.showCameraPermissionSettingsToast(requireActivity());
- }
- break;
- case PERMISSION_STORAGE:
- if (granted) storage.setChecked(true);
- else {
- storage.setChecked(false);
- PermissionUtils.showStoragePermissionSettingsToast(requireActivity());
- }
- break;
- case PERMISSION_LOCATION:
- if (granted) location.setChecked(true);
- else {
- location.setChecked(false);
- PermissionUtils.showLocationPermissionSettingsToast(requireActivity());
- }
- break;
- case PERMISSION_AUDIO:
- if (granted) mic.setChecked(true);
- else {
- mic.setChecked(false);
- PermissionUtils.showAudioPermissionSettingsToast(requireActivity());
- }
- break;
- }
- });
- });
+ result ->
+ result.forEach(
+ (permission, granted) -> {
+ switch (permission) {
+ case PERMISSION_CAMERA:
+ if (granted) camera.setChecked(true);
+ else {
+ camera.setChecked(false);
+ PermissionUtils.showCameraPermissionSettingsToast(requireActivity());
+ }
+ break;
+ case PERMISSION_STORAGE:
+ if (granted) storage.setChecked(true);
+ else {
+ storage.setChecked(false);
+ PermissionUtils.showStoragePermissionSettingsToast(requireActivity());
+ }
+ break;
+ case PERMISSION_LOCATION:
+ if (granted) location.setChecked(true);
+ else {
+ location.setChecked(false);
+ PermissionUtils.showLocationPermissionSettingsToast(requireActivity());
+ }
+ break;
+ case PERMISSION_AUDIO:
+ if (granted) mic.setChecked(true);
+ else {
+ mic.setChecked(false);
+ PermissionUtils.showAudioPermissionSettingsToast(requireActivity());
+ }
+ break;
+ }
+ }));
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
@@ -185,6 +188,41 @@ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
return false;
});
}
+
+ ListPreference streamMode = findPreference("video_server");
+
+ if (streamMode != null)
+ streamMode.setOnPreferenceChangeListener(
+ (preference, newValue) -> {
+ AlertDialog.Builder builder = new AlertDialog.Builder(requireActivity());
+ builder.setTitle(R.string.confirm_title);
+ builder.setMessage(R.string.stream_change_body);
+ builder.setPositiveButton(
+ "Yes",
+ (dialog, id) -> {
+ streamMode.setValue(newValue.toString());
+ restartApp();
+ });
+ builder.setNegativeButton(
+ "Cancel", (dialog, id) -> streamMode.setValue(streamMode.getEntry().toString()));
+ AlertDialog dialog = builder.create();
+ dialog.show();
+ return false;
+ });
+ }
+
+ private void restartApp() {
+ new Handler()
+ .postDelayed(
+ () -> {
+ final PackageManager pm = requireActivity().getPackageManager();
+ final Intent intent =
+ pm.getLaunchIntentForPackage(requireActivity().getPackageName());
+ requireActivity().finishAffinity(); // Finishes all activities.
+ requireActivity().startActivity(intent); // Start the launch activity
+ System.exit(0); // System finishes and automatically relaunches us.
+ },
+ 100);
}
@Override
diff --git a/android/app/src/main/java/org/openbot/objectNav/ObjectNavFragment.java b/android/app/src/main/java/org/openbot/objectNav/ObjectNavFragment.java
index a9d73d315..c93bbf984 100644
--- a/android/app/src/main/java/org/openbot/objectNav/ObjectNavFragment.java
+++ b/android/app/src/main/java/org/openbot/objectNav/ObjectNavFragment.java
@@ -497,8 +497,11 @@ protected void handleDriveCommand(Control control) {
vehicle.setControl(control);
float left = vehicle.getLeftSpeed();
float right = vehicle.getRightSpeed();
- binding.controllerContainer.controlInfo.setText(
- String.format(Locale.US, "%.0f,%.0f", left, right));
+ requireActivity()
+ .runOnUiThread(
+ () ->
+ binding.controllerContainer.controlInfo.setText(
+ String.format(Locale.US, "%.0f,%.0f", left, right)));
}
protected Model getModel() {
diff --git a/android/app/src/main/java/org/openbot/robot/FreeRoamFragment.java b/android/app/src/main/java/org/openbot/robot/FreeRoamFragment.java
index 9acfe3006..7452e80d3 100644
--- a/android/app/src/main/java/org/openbot/robot/FreeRoamFragment.java
+++ b/android/app/src/main/java/org/openbot/robot/FreeRoamFragment.java
@@ -274,12 +274,14 @@ protected void processControllerKeyData(String commandType) {
handleDriveCommand();
setControlMode(ControlMode.GAMEPAD);
break;
+
case Constants.CMD_SPEED_DOWN:
setSpeedMode(
Enums.toggleSpeed(
Enums.Direction.DOWN.getValue(),
Enums.SpeedMode.getByID(preferencesManager.getSpeedMode())));
break;
+
case Constants.CMD_SPEED_UP:
setSpeedMode(
Enums.toggleSpeed(
diff --git a/android/app/src/main/java/org/openbot/tflite/Autopilot.java b/android/app/src/main/java/org/openbot/tflite/Autopilot.java
index 2c85ea6a4..d8ab9df6b 100644
--- a/android/app/src/main/java/org/openbot/tflite/Autopilot.java
+++ b/android/app/src/main/java/org/openbot/tflite/Autopilot.java
@@ -11,6 +11,7 @@
import java.nio.ByteOrder;
import java.util.Arrays;
import org.openbot.env.Control;
+import timber.log.Timber;
public abstract class Autopilot extends Network {
@@ -24,8 +25,11 @@ public abstract class Autopilot extends Network {
* @return A detector with the desired configuration.
*/
- /** A ByteBuffer to hold image data, to be feed into Tensorflow Lite as inputs. */
- protected ByteBuffer indicatorBuffer = null;
+ /** A ByteBuffer to hold data, to be feed into Tensorflow Lite as inputs. */
+ protected ByteBuffer cmdBuffer = null;
+
+ private int cmdIndex;
+ private int imgIndex;
public static Autopilot create(Activity activity, Model model, Device device, int numThreads)
throws IOException, IllegalArgumentException {
@@ -36,25 +40,30 @@ public static Autopilot create(Activity activity, Model model, Device device, in
protected Autopilot(Activity activity, Model model, Device device, int numThreads)
throws IOException, IllegalArgumentException {
super(activity, model, device, numThreads);
-
- tflite.getInputIndex("cmd_input");
+ try {
+ cmdIndex = tflite.getInputIndex("serving_default_cmd_input:0");
+ imgIndex = tflite.getInputIndex("serving_default_img_input:0");
+ } catch (Exception e) {
+ cmdIndex = tflite.getInputIndex("cmd_input");
+ imgIndex = tflite.getInputIndex("img_input");
+ }
if (!Arrays.equals(
- tflite.getInputTensor(tflite.getInputIndex("img_input")).shape(),
+ tflite.getInputTensor(imgIndex).shape(),
new int[] {1, getImageSizeY(), getImageSizeX(), 3}))
throw new IllegalArgumentException("Invalid tensor dimensions");
- indicatorBuffer = ByteBuffer.allocateDirect(4);
- indicatorBuffer.order(ByteOrder.nativeOrder());
+ cmdBuffer = ByteBuffer.allocateDirect(4);
+ cmdBuffer.order(ByteOrder.nativeOrder());
- LOGGER.d("Created a Tensorflow Lite Autopilot.");
+ Timber.d("Created a Tensorflow Lite Autopilot.");
}
private void convertIndicatorToByteBuffer(int indicator) {
- if (indicatorBuffer == null) {
+ if (cmdBuffer == null) {
return;
}
- indicatorBuffer.rewind();
- indicatorBuffer.putFloat(indicator);
+ cmdBuffer.rewind();
+ cmdBuffer.putFloat(indicator);
}
public Control recognizeImage(final Bitmap bitmap, final int indicator) {
@@ -69,10 +78,10 @@ public Control recognizeImage(final Bitmap bitmap, final int indicator) {
Trace.beginSection("runInference");
long startTime = SystemClock.elapsedRealtime();
Object[] inputArray;
- if (tflite.getInputIndex("cmd_input") == 0) {
- inputArray = new Object[] {indicatorBuffer, imgData};
+ if (cmdIndex == 0) {
+ inputArray = new Object[] {cmdBuffer, imgData};
} else {
- inputArray = new Object[] {imgData, indicatorBuffer};
+ inputArray = new Object[] {imgData, cmdBuffer};
}
float[][] predicted_ctrl = new float[1][2];
@@ -80,7 +89,7 @@ public Control recognizeImage(final Bitmap bitmap, final int indicator) {
tflite.runForMultipleInputsOutputs(inputArray, outputMap);
long endTime = SystemClock.elapsedRealtime();
Trace.endSection();
- LOGGER.v("Timecost to run model inference: " + (endTime - startTime));
+ Timber.v("Timecost to run model inference: %s", (endTime - startTime));
Trace.endSection(); // "recognizeImage"
return new Control(predicted_ctrl[0][0], predicted_ctrl[0][1]);
diff --git a/android/app/src/main/java/org/openbot/tflite/Detector.java b/android/app/src/main/java/org/openbot/tflite/Detector.java
index 0beddab7e..18eb3d00f 100755
--- a/android/app/src/main/java/org/openbot/tflite/Detector.java
+++ b/android/app/src/main/java/org/openbot/tflite/Detector.java
@@ -30,6 +30,7 @@
import java.util.List;
import java.util.Locale;
import java.util.PriorityQueue;
+import timber.log.Timber;
/**
* Wrapper for frozen detection models trained using the Tensorflow Object Detection API:
@@ -158,7 +159,7 @@ protected Detector(Activity activity, Model model, Device device, int numThreads
labels = loadLabelList(activity);
parseTflite();
- LOGGER.d("Created a Tensorflow Lite Detector.");
+ Timber.d("Created a Tensorflow Lite Detector.");
}
/** Reads label list from Assets. */
@@ -194,7 +195,7 @@ public List recognizeImage(final Bitmap bitmap, String className)
runInference();
long endTime = SystemClock.elapsedRealtime();
Trace.endSection();
- LOGGER.v("Timecost to run model inference: " + (endTime - startTime));
+ Timber.v("Timecost to run model inference: %s", (endTime - startTime));
Trace.endSection(); // "recognizeImage"
return getRecognitions(className);
diff --git a/android/app/src/main/java/org/openbot/utils/Enums.java b/android/app/src/main/java/org/openbot/utils/Enums.java
index b5944ba23..ed9f5db95 100644
--- a/android/app/src/main/java/org/openbot/utils/Enums.java
+++ b/android/app/src/main/java/org/openbot/utils/Enums.java
@@ -4,6 +4,31 @@
import java.util.EnumSet;
public class Enums {
+ public enum SensorType {
+ ACCELEROMETER("Accelerometer"),
+ GYROSCOPE("Gyroscope"),
+ PROXIMITY("Proximity"),
+ GRAVITY("Gravity"),
+ MAGNETIC("Magnetic"),
+ LIGHT("Light"),
+ PRESSURE("Pressure"),
+ TEMPERATURE("Temperature"),
+ GPS("Gps"),
+ VEHICLE("Vehicle"),
+ POSE("Pose"),
+ MOTION("Motion");
+
+ private String sensor;
+
+ SensorType(String sensor) {
+ this.sensor = sensor;
+ }
+
+ public String getSensor() {
+ return sensor;
+ }
+ }
+
public enum LogMode {
ALL_IMGS(0),
CROP_IMG(1),
diff --git a/android/app/src/main/res/drawable/button_background_border.xml b/android/app/src/main/res/drawable/button_background_border.xml
new file mode 100755
index 000000000..914ded38d
--- /dev/null
+++ b/android/app/src/main/res/drawable/button_background_border.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/android/app/src/main/res/layout-land/dialog_sensors.xml b/android/app/src/main/res/layout-land/dialog_sensors.xml
new file mode 100644
index 000000000..1baefce29
--- /dev/null
+++ b/android/app/src/main/res/layout-land/dialog_sensors.xml
@@ -0,0 +1,117 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android/app/src/main/res/layout-land/fragment_logger.xml b/android/app/src/main/res/layout-land/fragment_logger.xml
index fb3df832d..c93d87f37 100644
--- a/android/app/src/main/res/layout-land/fragment_logger.xml
+++ b/android/app/src/main/res/layout-land/fragment_logger.xml
@@ -144,16 +144,18 @@
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent">
+
+ android:text="Preview Images"
+ app:layout_constraintBottom_toTopOf="@+id/trainingDataCheckBox"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toEndOf="@+id/ipAddress"
+ app:layout_constraintTop_toTopOf="parent" />
+
-
-
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@+id/sensorDataButton" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android/app/src/main/res/layout/fragment_logger.xml b/android/app/src/main/res/layout/fragment_logger.xml
index f09d052a1..5cbbf0df4 100644
--- a/android/app/src/main/res/layout/fragment_logger.xml
+++ b/android/app/src/main/res/layout/fragment_logger.xml
@@ -55,8 +55,8 @@
android:id="@+id/logger_switch"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_margin="8dp"
android:padding="8dp"
+ android:layout_margin="8dp"
android:text="Log Data"
app:layout_constraintBottom_toTopOf="@+id/previewText"
app:layout_constraintStart_toStartOf="parent"
@@ -139,20 +139,38 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
+ android:layout_marginStart="16dp"
+ android:layout_marginEnd="16dp"
+ android:layout_marginBottom="16dp"
+ android:orientation="vertical"
app:layout_constraintBottom_toTopOf="@+id/controllerContainer"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent">
+
+
+ android:text="Preview Images"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toEndOf="@+id/ipAddress"
+ app:layout_constraintTop_toTopOf="parent" />
-
-
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@+id/sensorDataButton" />
+
+
+
+
+
\ No newline at end of file
diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml
index 938785ad4..17e6365f0 100755
--- a/android/app/src/main/res/values/strings.xml
+++ b/android/app/src/main/res/values/strings.xml
@@ -132,6 +132,9 @@
PermissionsGoing back would cancel the download. Are you sure?Model Download In Progress
+ Save
+ Are you sure?
+ The app needs to be restarted for this setting to take effect
diff --git a/android/build.gradle b/android/build.gradle
index 478bc9838..363977e73 100755
--- a/android/build.gradle
+++ b/android/build.gradle
@@ -8,7 +8,7 @@ buildscript {
mavenLocal()
}
dependencies {
- classpath 'com.android.tools.build:gradle:4.1.3'
+ classpath 'com.android.tools.build:gradle:4.2.2'
classpath 'com.google.gms:google-services:4.3.5'
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.4.1'
classpath 'de.undercouch:gradle-download-task:4.0.2'
diff --git a/android/controller/README.md b/android/controller/README.md
index 726b6689c..bb4eadc87 100644
--- a/android/controller/README.md
+++ b/android/controller/README.md
@@ -1,10 +1,6 @@
# Controller App
-This Android app serves as a `remote controller` for the [OpenBot](https://www.openbot.org) vehicle. Basically it preforms similar function as PS3/4 or Xbox remote controller, but running on another Android device. It allows the user to control the robot car via two sliders in `Dual Drive` mode.
-
-
-
-
+This Android app serves as a `remote controller` for the [OpenBot](https://www.openbot.org) vehicle. Basically it performs similar function as PS3/4 or Xbox remote controller, but running on another Android device.
## Connection
@@ -14,37 +10,57 @@ When the controller app is started, it immediately tries to connect to the robot
-To connect the controller to the robot, place the robot's app control setting into a **Phone** mode.
+To connect the controller to the robot, place the robot's app control setting into the **Phone** mode.
+You can also connect to the controller from the `FreeRoamFragment` by selecting the phone as the controller:
+
+
+
+
+
In a few seconds, you will hear a beep, and the controller will change its screen to:
-... and then to the screen with the drive controls.
+Here you can select to drive the robot by tilting the phone, or by using the on-screen controls.
-***Note:*** This should be sufficient to connect, but if the connection cannot be established after 30 seconds, toggle the `Control` setting on the bot app to `Gamepad`
-and then to `Phone` again to re-initiate the connection. If that fails, exit the controller app and start it again. Toggle the Control mode again on the robot app.
+***Note:*** This should be sufficient to connect, but if the connection cannot be established after 30 seconds, toggle the `Control` setting on the bot app to `Gamepad` and then to `Phone` again to re-initiate the connection. If that fails, exit the controller app and start it again. Toggle the Control mode again on the robot app.
## Operation
-The operation of the controller app is pretty self-explanatory. You can turn left/right by moving the slider thumb up and down on each side. You can also place the wheels
-on each side in reverse by moving the thumb below the center of the slider.
+### On-screen controls
+This mode allows the user to control the robot car via two sliders in `Dual Drive` mode. You can turn left/right by moving the slider thumb up and down on each side. The wheels on each side turn forward/backward when moving the thumb above/below the center of the slider.
+
+
+
+
-You can also set right/left
+You can also set the left/right turn indicators
-turn indicators by clicking on the arrows on the top-left of the screen, and the red button between them to cancel.
+by clicking on the arrows on the top-left of the screen, and the red button between them to cancel.
+
+### Tilt to drive
+The controller can also use its accelerometer motion sensor to drive the robot. If you select this option, the controller will enter a full-screen (Zen) mode with only the video showing and a `brake` and `accelerator` pedals. To exit this mode, double-tap on the screen.
+
+Here is a picture of the `tilt mode` screen:
+
+
+
+
+
+Use the `accelerator` and `brake` buttons to move forward/backward.
-If you like to control some other settings on the robot, such as `Noise`, `Logs`, etc, double-tap in the middle of the screen, and you will see the buttons on the left.
-They also show the current settings, so if the `Noise` button is highlighted, this means this setting on the robot is `ON`. If you change the setting manually from
- the robot menu, this should be reflected on the controller app as well.
+- Pressing the `acceletator` will accelerate the robot to full speed within 2 seconds. When you release the button, the robot will slow down to a stop (stop speed set to 0% of the maximum speed, can be adjusted).
+- Pressing the `brake` button will immedately stop the robot. If we hold the brake for another second, the robot will start moving backwards until it reaches the maximim reverse speed in one second. When we let go of the brake, the robot will come to a stop.
+- We steer the robot by tilting the controller left or right.
## Future Development
@@ -55,4 +71,4 @@ Some of the features we are looking to add are:
- Use controller's gyroscope sensor co control the robot
- Send crash and bump events from the robot to the controller for a more realistic experience
-Here is a [Technical Overview](../../docs/technical/OpenBotController.pdf) of the controller app.
\ No newline at end of file
+Here is a [Technical Overview](../../docs/technical/OpenBotController.pdf) of the controller app.
diff --git a/android/controller/src/main/java/org/openbot/controller/ControllerActivity.kt b/android/controller/src/main/java/org/openbot/controller/ControllerActivity.kt
index 8fb6fea50..072bd07f7 100644
--- a/android/controller/src/main/java/org/openbot/controller/ControllerActivity.kt
+++ b/android/controller/src/main/java/org/openbot/controller/ControllerActivity.kt
@@ -25,7 +25,7 @@ import org.openbot.controller.customComponents.DualDriveSeekBar
import org.openbot.controller.customComponents.VideoViewVlc
import org.openbot.controller.customComponents.VideoViewWebRTC
import org.openbot.controller.databinding.ActivityFullscreenBinding
-import org.openbot.controller.utils.EventProcessor
+import org.openbot.controller.utils.LocalEventBus
import org.openbot.controller.utils.Utils
import kotlin.system.exitProcess
@@ -34,7 +34,7 @@ class ControllerActivity : /*AppCompat*/
private val PERMISSION_REQUEST_LOCATION = 101
private val TAG = "ControllerActivity"
private lateinit var binding: ActivityFullscreenBinding
- private lateinit var screenManager: ScreenManager
+ private lateinit var screenSelector: ScreenSelector
@SuppressLint("ClickableViewAccessibility")
override fun onCreate(savedInstanceState: Bundle?) {
@@ -44,10 +44,10 @@ class ControllerActivity : /*AppCompat*/
setupPermissions()
- screenManager = ScreenManager(binding)
+ screenSelector = ScreenSelector(binding)
ConnectionSelector.init(this)
- AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)
+ AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
createAppEventsSubscription()
@@ -56,7 +56,8 @@ class ControllerActivity : /*AppCompat*/
binding.leftDriveControl.setDirection(DualDriveSeekBar.LeftOrRight.LEFT)
binding.rightDriveControl.setDirection(DualDriveSeekBar.LeftOrRight.RIGHT)
- screenManager.hideControls()
+ screenSelector.hideControls()
+
hideSystemUI()
BotDataListener.init()
@@ -100,26 +101,6 @@ class ControllerActivity : /*AppCompat*/
return view
}
-// private fun createView(view: View): View {
-// val existingView = findViewById(R.id.video_view)
-// if (existingView != null && existingView::class == view::class) {
-// return existingView // already exist and of same type
-// }
-// view.id = R.id.video_view
-//
-// val layoutParams = ViewGroup.LayoutParams(
-// LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT
-// )
-// view.layoutParams = layoutParams
-//
-// if (binding.video.childCount > 0) {
-// binding.video.removeAllViews()
-// }
-// binding.video.addView(view)
-//
-// return view
-// }
-
private fun setupPermissions() {
val permission = ContextCompat.checkSelfPermission(
this,
@@ -144,43 +125,52 @@ class ControllerActivity : /*AppCompat*/
private fun subscribeToStatusInfo() {
StatusEventBus.addSubject("CONNECTION_ACTIVE")
StatusEventBus.subscribe(this.javaClass.simpleName, "CONNECTION_ACTIVE", onNext = {
- if (it.toBoolean()) screenManager.showControlsImmediately() else screenManager.hideControls()
+ if (it.toBoolean()) {
+ screenSelector.showControls()
+ } else {
+ screenSelector.hideControls()
+ binding.controlModeTiltLayout.stop()
+ }
})
}
@SuppressLint("CheckResult")
private fun createAppEventsSubscription() {
- EventProcessor.subscriber.start(
+ LocalEventBus.subscriber.start(
this.javaClass.simpleName,
{
Log.i(TAG, "Got $it event")
when (it) {
- EventProcessor.ProgressEvents.ConnectionSuccessful -> {
+ LocalEventBus.ProgressEvents.ConnectionSuccessful -> {
Utils.beep()
- screenManager.showControls()
+ screenSelector.showControls()
}
- EventProcessor.ProgressEvents.ConnectionStarted -> {
+ LocalEventBus.ProgressEvents.ConnectionStarted -> {
}
- EventProcessor.ProgressEvents.ConnectionFailed -> {
- screenManager.hideControls()
+ LocalEventBus.ProgressEvents.ConnectionFailed -> {
+ screenSelector.hideControls()
}
- EventProcessor.ProgressEvents.StartAdvertising -> {
- screenManager.hideControls()
+ LocalEventBus.ProgressEvents.StartAdvertising -> {
+ screenSelector.hideControls()
}
- EventProcessor.ProgressEvents.Disconnected -> {
- screenManager.hideControls()
+ LocalEventBus.ProgressEvents.Disconnected -> {
+ screenSelector.hideControls()
+ binding.controlModeTiltLayout.stop()
ConnectionSelector.getConnection().connect(this)
}
- EventProcessor.ProgressEvents.StopAdvertising -> {
+ LocalEventBus.ProgressEvents.StopAdvertising -> {
}
- EventProcessor.ProgressEvents.TemporaryConnectionProblem -> {
- screenManager.hideControls()
+ LocalEventBus.ProgressEvents.TemporaryConnectionProblem -> {
+ screenSelector.hideControls()
ConnectionSelector.getConnection().connect(this)
}
- EventProcessor.ProgressEvents.AdvertisingFailed -> {
- screenManager.hideControls()
+ LocalEventBus.ProgressEvents.PhoneOnTable -> {
+ screenSelector.showControls()
+ }
+ LocalEventBus.ProgressEvents.AdvertisingFailed -> {
+ screenSelector.hideControls()
}
}
},
@@ -236,7 +226,6 @@ class ControllerActivity : /*AppCompat*/
PERMISSION_REQUEST_LOCATION -> {
if (grantResults.isEmpty() || grantResults[0] != PackageManager.PERMISSION_GRANTED) {
-
Log.i(TAG, "Permission has been denied by user")
finish()
exitProcess(0)
diff --git a/android/controller/src/main/java/org/openbot/controller/DriveCommandReducer.kt b/android/controller/src/main/java/org/openbot/controller/DriveCommandReducer.kt
new file mode 100644
index 000000000..331fd5286
--- /dev/null
+++ b/android/controller/src/main/java/org/openbot/controller/DriveCommandReducer.kt
@@ -0,0 +1,27 @@
+package org.openbot.controller
+
+import android.util.Log
+import org.openbot.controller.customComponents.DualDriveSeekBar
+import kotlin.math.absoluteValue
+
+object DriveCommandReducer {
+ private var lastRight = 0f
+ private var lastLeft = 0f
+ private const val withinRange = .02f
+
+ fun filter(rightValue: Float, leftValue: Float) {
+ if (isDifferent(rightValue, rightValue)) {
+ lastLeft = leftValue
+ lastRight = rightValue
+ val msg = "{driveCmd: {r:${rightValue}, l:${leftValue}}}"
+ ConnectionSelector.getConnection().sendMessage(msg)
+ }
+ }
+
+ private fun isDifferent (right: Float, left: Float): Boolean {
+ if ((left - lastLeft).absoluteValue <= withinRange && (right - lastRight).absoluteValue <= withinRange) {
+ return false
+ }
+ return true
+ }
+}
diff --git a/android/controller/src/main/java/org/openbot/controller/ForwardSpeed.kt b/android/controller/src/main/java/org/openbot/controller/ForwardSpeed.kt
new file mode 100644
index 000000000..34fffe0c9
--- /dev/null
+++ b/android/controller/src/main/java/org/openbot/controller/ForwardSpeed.kt
@@ -0,0 +1,53 @@
+package org.openbot.controller
+
+import android.annotation.SuppressLint
+import android.util.Log
+import androidx.annotation.FloatRange
+import kotlin.math.roundToInt
+
+object ForwardSpeed {
+ const val max = 1f
+ const val min = 0f
+ const val minNegative = -1f
+
+ @FloatRange(from=min.toDouble(),to=max.toDouble())
+ var value = 0f
+
+ fun increment(incrementValue: Float) {
+ value = max.coerceAtMost(value + incrementValue).round(2)
+ }
+
+ fun decrement(decrementValue: Float) {
+ value = min.coerceAtLeast(value - decrementValue).round(2)
+ }
+
+ fun decrementNegative(decrementValue: Float) {
+ value = minNegative.coerceAtLeast(value - decrementValue).round(2)
+ }
+
+ fun reset() {
+ value = 0f
+ }
+
+ fun setTo(minSpeed: Float) {
+ if (minSpeed !in -1f..1f) {
+ Log.e("ForwardSpeed", "setTo () got invalid parameter $minSpeed")
+ return
+ }
+ value = minSpeed
+ }
+
+ fun isMax(): Boolean {
+ return value >= max
+ }
+
+ fun isMin(): Boolean {
+ return value <= min
+ }
+
+ private fun Float.round(decimals: Int): Float {
+ var multiplier:Int = 1
+ repeat(decimals) { multiplier *= 10 }
+ return kotlin.math.round(this * multiplier) / multiplier
+ }
+}
\ No newline at end of file
diff --git a/android/controller/src/main/java/org/openbot/controller/NearbyConnection.kt b/android/controller/src/main/java/org/openbot/controller/NearbyConnection.kt
index 7b6a36b50..0eab4ae19 100644
--- a/android/controller/src/main/java/org/openbot/controller/NearbyConnection.kt
+++ b/android/controller/src/main/java/org/openbot/controller/NearbyConnection.kt
@@ -13,10 +13,9 @@ import android.content.Context
import android.util.Log
import com.google.android.gms.nearby.Nearby
import com.google.android.gms.nearby.connection.*
-import org.openbot.controller.utils.EventProcessor
+import org.openbot.controller.utils.LocalEventBus
import org.openbot.controller.utils.Utils
import java.nio.charset.StandardCharsets
-import kotlin.reflect.KProperty0
object NearbyConnection : ILocalConnection {
private var dataReceivedCallback: IDataReceived? = null
@@ -84,9 +83,9 @@ object NearbyConnection : ILocalConnection {
TAG,
"onConnectionResult: connection successful"
)
- val event: EventProcessor.ProgressEvents =
- EventProcessor.ProgressEvents.ConnectionSuccessful
- EventProcessor.onNext(event)
+ val event: LocalEventBus.ProgressEvents =
+ LocalEventBus.ProgressEvents.ConnectionSuccessful
+ LocalEventBus.onNext(event)
connectionsClient!!.stopAdvertising()
pairedDeviceEndpointId = endpointId
@@ -94,9 +93,9 @@ object NearbyConnection : ILocalConnection {
} else {
abortConnection()
- val event: EventProcessor.ProgressEvents =
- EventProcessor.ProgressEvents.ConnectionFailed
- EventProcessor.onNext(event)
+ val event: LocalEventBus.ProgressEvents =
+ LocalEventBus.ProgressEvents.ConnectionFailed
+ LocalEventBus.onNext(event)
Log.i(
TAG,
@@ -106,9 +105,9 @@ object NearbyConnection : ILocalConnection {
}
override fun onDisconnected(endpointId: String) {
- val event: EventProcessor.ProgressEvents =
- EventProcessor.ProgressEvents.Disconnected
- EventProcessor.onNext(event)
+ val event: LocalEventBus.ProgressEvents =
+ LocalEventBus.ProgressEvents.Disconnected
+ LocalEventBus.onNext(event)
Log.i(
TAG,
@@ -123,9 +122,9 @@ object NearbyConnection : ILocalConnection {
}
override fun disconnect(context: Context?) {
- val event: EventProcessor.ProgressEvents =
- EventProcessor.ProgressEvents.Disconnecting
- EventProcessor.onNext(event)
+ val event: LocalEventBus.ProgressEvents =
+ LocalEventBus.ProgressEvents.Disconnecting
+ LocalEventBus.onNext(event)
connectionsClient?.stopAdvertising()
@@ -150,16 +149,16 @@ object NearbyConnection : ILocalConnection {
.addOnSuccessListener {
Log.d("startAdvertising", "We're advertising")
- val event: EventProcessor.ProgressEvents =
- EventProcessor.ProgressEvents.StartAdvertising
- EventProcessor.onNext(event)
+ val event: LocalEventBus.ProgressEvents =
+ LocalEventBus.ProgressEvents.StartAdvertising
+ LocalEventBus.onNext(event)
}.addOnFailureListener {
abortConnection()
- val event: EventProcessor.ProgressEvents =
- EventProcessor.ProgressEvents.AdvertisingFailed
- EventProcessor.onNext(event)
+ val event: LocalEventBus.ProgressEvents =
+ LocalEventBus.ProgressEvents.AdvertisingFailed
+ LocalEventBus.onNext(event)
Log.d(
"startAdvertising",
diff --git a/android/controller/src/main/java/org/openbot/controller/NetworkServiceConnection.kt b/android/controller/src/main/java/org/openbot/controller/NetworkServiceConnection.kt
index c54cdbf88..1c1dd4c26 100644
--- a/android/controller/src/main/java/org/openbot/controller/NetworkServiceConnection.kt
+++ b/android/controller/src/main/java/org/openbot/controller/NetworkServiceConnection.kt
@@ -7,7 +7,7 @@ import android.net.nsd.NsdManager
import android.net.nsd.NsdManager.RegistrationListener
import android.net.nsd.NsdServiceInfo
import android.util.Log
-import org.openbot.controller.utils.EventProcessor
+import org.openbot.controller.utils.LocalEventBus
import org.openbot.controller.utils.Utils
import java.io.BufferedInputStream
import java.io.DataInputStream
@@ -114,9 +114,9 @@ object NetworkServiceConnection : ILocalConnection {
clientInfo = ClientInfo(reader, writer)
- val event: EventProcessor.ProgressEvents =
- EventProcessor.ProgressEvents.ConnectionSuccessful
- EventProcessor.onNext(event)
+ val event: LocalEventBus.ProgressEvents =
+ LocalEventBus.ProgressEvents.ConnectionSuccessful
+ LocalEventBus.onNext(event)
println("Client connected: ${client.inetAddress.hostAddress}")
} catch (e: Exception) {
@@ -172,9 +172,9 @@ object NetworkServiceConnection : ILocalConnection {
}
serverSocket.close()
- val event: EventProcessor.ProgressEvents =
- EventProcessor.ProgressEvents.Disconnected
- EventProcessor.onNext(event)
+ val event: LocalEventBus.ProgressEvents =
+ LocalEventBus.ProgressEvents.Disconnected
+ LocalEventBus.onNext(event)
}
fun put(message: String?) {
@@ -207,9 +207,9 @@ object NetworkServiceConnection : ILocalConnection {
override fun onRegistrationFailed(serviceInfo: NsdServiceInfo, errorCode: Int) {
Log.d(TAG, "onRegistrationFailed")
- val event: EventProcessor.ProgressEvents =
- EventProcessor.ProgressEvents.ConnectionFailed
- EventProcessor.onNext(event)
+ val event: LocalEventBus.ProgressEvents =
+ LocalEventBus.ProgressEvents.ConnectionFailed
+ LocalEventBus.onNext(event)
}
override fun onServiceUnregistered(serviceInfo: NsdServiceInfo) {
diff --git a/android/controller/src/main/java/org/openbot/controller/PhoneSensorToDualDriveConverter.kt b/android/controller/src/main/java/org/openbot/controller/PhoneSensorToDualDriveConverter.kt
new file mode 100644
index 000000000..d50bc2f4f
--- /dev/null
+++ b/android/controller/src/main/java/org/openbot/controller/PhoneSensorToDualDriveConverter.kt
@@ -0,0 +1,69 @@
+package org.openbot.controller
+
+class PhoneSensorToDualDriveConverter {
+ private val g = 9.81f
+
+ fun convert(
+ azimuth: Float?,
+ pitch: Float?,
+ roll: Float?
+ ): DualDriveValues {
+ var leftSpeed = 0f
+ var rightSpeed = 0f
+ var forwardSpeed = 0f
+
+ if (inDeadZone(roll!!)) {
+ return DualDriveValues(0f, 0f)
+ }
+
+ // get forward speed
+ forwardSpeed = ForwardSpeed.value
+
+ // adjust for turning
+ leftSpeed = forwardSpeed + (pitch?.div(g / 2) ?: 0f) * forwardSpeed
+ rightSpeed = forwardSpeed - (pitch?.div(g / 2) ?: 0f) * forwardSpeed
+
+ return DualDriveValues(leftSpeed, rightSpeed)
+ }
+
+ inner class DualDriveValues(var left: Float, var right: Float) {
+ private val MAX = 1.0f
+ private val MIN = -1.0f
+
+ init {
+ left = clean(left)
+ right = clean(right)
+ }
+
+ private fun clean(value: Float): Float {
+ var ret = value
+
+ if (value > MAX) {
+ ret = MAX
+ }
+ if (value < MIN) {
+ ret = MIN
+ }
+ return ret.round(3)
+ }
+
+ private fun Float.round(decimals: Int): Float {
+ var multiplier = 1.0f
+ repeat(decimals) { multiplier *= 10 }
+ return kotlin.math.round(this * multiplier) / multiplier
+ }
+
+ fun reset() {
+ left = 0f
+ right = 0f
+ }
+ }
+
+ fun inDeadZone(roll: Float): Boolean {
+ return isWithin(roll, 0f, 1f)
+ }
+
+ private fun isWithin(value: Float?, desiredValue: Float?, tolerance: Float?): Boolean {
+ return value!! in (desiredValue!! - tolerance!!)..(desiredValue + tolerance)
+ }
+}
diff --git a/android/controller/src/main/java/org/openbot/controller/ScreenManager.kt b/android/controller/src/main/java/org/openbot/controller/ScreenManager.kt
deleted file mode 100644
index 02d0cb83b..000000000
--- a/android/controller/src/main/java/org/openbot/controller/ScreenManager.kt
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Developed for the OpenBot project (https://openbot.org) by:
- *
- * Ivo Zivkov
- * izivkov@gmail.com
- *
- * Date: 2020-12-27, 10:59 p.m.
- */
-
-package org.openbot.controller
-
-import android.os.Handler
-import android.os.Looper
-import org.openbot.controller.databinding.ActivityFullscreenBinding
-
-data class ScreenManager (val binding: ActivityFullscreenBinding) {
-
- private var buttonsVisible: Boolean = false
-
- init {
- binding.mainScreen.setupDoubleTap(::toggleButtons)
- }
-
- private fun toggleButtons() {
- if (buttonsVisible) {
- hideButtons()
- } else {
- showButtons()
- }
- }
-
- private fun hideButtons() {
- binding.botSetupButtons.hide()
- showSliders()
- buttonsVisible = false
- }
-
- private fun hideSliders() {
- binding.driveModeControls.hide()
- }
-
- private fun showSliders() {
- binding.driveModeControls.show()
- }
-
- private fun showButtons(milliseconds: Long) {
- showButtons()
-
- Handler(Looper.getMainLooper()).postDelayed({
- hideButtons()
- }, milliseconds)
- }
-
- private fun showButtons() {
- binding.botSetupButtons.show()
-
- hideSliders()
- buttonsVisible = true
- }
-
- fun hideControls() {
- binding.mainScreen.hide()
- binding.splashScreen.show()
- }
-
- fun showControls() {
- binding.splashScreen.hide()
- binding.mainScreen.show()
-
- showButtons(3000)
- }
-
- fun showControlsImmediately () {
- binding.splashScreen.hide()
- binding.mainScreen.show()
- }
-}
diff --git a/android/controller/src/main/java/org/openbot/controller/ScreenSelector.kt b/android/controller/src/main/java/org/openbot/controller/ScreenSelector.kt
new file mode 100644
index 000000000..6deb9a811
--- /dev/null
+++ b/android/controller/src/main/java/org/openbot/controller/ScreenSelector.kt
@@ -0,0 +1,85 @@
+/*
+ * Developed for the OpenBot project (https://openbot.org) by:
+ *
+ * Ivo Zivkov
+ * izivkov@gmail.com
+ *
+ * Date: 2020-12-27, 10:59 p.m.
+ */
+
+package org.openbot.controller
+
+import android.annotation.SuppressLint
+import android.util.Log
+import org.openbot.controller.databinding.ActivityFullscreenBinding
+import org.openbot.controller.utils.LocalEventBus
+
+data class ScreenSelector (val binding: ActivityFullscreenBinding) {
+
+ init {
+ binding.mainScreen.setupDoubleTap(::showButtons)
+ monitorDriveMode()
+ }
+
+ private fun hideButtons() {
+ binding.botSetupButtons?.hide()
+ }
+
+ private fun hideSliders() {
+ binding.driveModeSlidersLayout.hide()
+ binding.controlModeTiltLayout.hide()
+ }
+
+ private fun showButtons() {
+ binding.controlModeTiltLayout.stop()
+ binding.botSetupButtons?.show()
+ hideSliders()
+ }
+
+ fun hideControls() {
+ binding.mainScreen.hide()
+ binding.splashScreen.show()
+ }
+
+ fun showControls() {
+ binding.splashScreen.hide()
+ binding.mainScreen.show()
+
+ showButtons()
+ }
+
+ @SuppressLint("CheckResult")
+ private fun subscribe(subject: String, onDataReceived: (String) -> Unit) {
+ StatusEventBus.addSubject(subject)
+ StatusEventBus.subscribe(this.javaClass.simpleName, subject, onNext = {
+ onDataReceived(it as String)
+ })
+ }
+
+ private fun monitorDriveMode() {
+
+ LocalEventBus.subscriber.start(
+ this.javaClass.simpleName,
+ {
+ when (it) {
+ LocalEventBus.ProgressEvents.TiltControl -> {
+ binding.driveModeSlidersLayout.hide()
+ binding.controlModeTiltLayout.start()
+ binding.doubleTapMessage?.start()
+ }
+ LocalEventBus.ProgressEvents.SlidersControl -> {
+ binding.driveModeSlidersLayout.show()
+ binding.controlModeTiltLayout.stop()
+ }
+ }
+
+ binding.botSetupButtons?.hide()
+ },
+ { throwable ->
+ Log.d(
+ "EventsSubscription",
+ "Got error on subscribe: $throwable"
+ )
+ })
+ }
+}
diff --git a/android/controller/src/main/java/org/openbot/controller/customComponents/Accelerator.kt b/android/controller/src/main/java/org/openbot/controller/customComponents/Accelerator.kt
new file mode 100644
index 000000000..41cad3ec8
--- /dev/null
+++ b/android/controller/src/main/java/org/openbot/controller/customComponents/Accelerator.kt
@@ -0,0 +1,99 @@
+/*
+ * Developed for the OpenBot project (https://openbot.org) by:
+ *
+ * Ivo Zivkov
+ * izivkov@gmail.com
+ *
+ * Date: 2020-12-27, 10:57 p.m.
+ */
+
+package org.openbot.controller.customComponents
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.MotionEvent
+import androidx.annotation.FloatRange
+import androidx.annotation.IntRange
+import org.openbot.controller.ForwardSpeed
+import java.util.concurrent.Executors
+import java.util.concurrent.ScheduledExecutorService
+import java.util.concurrent.ScheduledFuture
+import java.util.concurrent.TimeUnit
+
+
+class Accelerator @JvmOverloads constructor(
+ context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
+) : ButtonWithBorder(context, attrs, defStyleAttr) {
+
+ data class DrivingCharacteristics constructor(
+ @IntRange(
+ from = 0,
+ to = 20000
+ ) val accelerationTime: Long = 1000, // time to full acceleration after the user has pressed teh button
+
+ @IntRange(
+ from = 1,
+ to = 10
+ ) val stepsToFullAcceleration: Int = 5, // in haw many steps we like to get to max acceleration
+
+ val decelerateAfterReleasingTheButton: Boolean = true, // do we want to slow down when the button is released?
+ @IntRange(
+ from = 0,
+ to = 5000
+ ) val decelerationTime: Long = 1000, // how many milliseconds to decelerate to min speed.
+
+ @FloatRange(
+ from = 0.0,
+ to = 1.0
+ ) val minSpeed: Float = .0f, // what percentage of full speed is our final decelerated speed (.1 means 10%)
+ )
+
+ private val drivingCharacteristics: DrivingCharacteristics = DrivingCharacteristics()
+ private lateinit var acceleratorStepTask: AcceleratorStepTask
+
+ init {
+ offState()
+ }
+
+ override fun onTouchEvent(event: MotionEvent): Boolean {
+ when (event.action) {
+ MotionEvent.ACTION_DOWN -> {
+ onState()
+
+ ForwardSpeed.reset()
+
+ val accelerationPeriod = drivingCharacteristics.accelerationTime / drivingCharacteristics.stepsToFullAcceleration
+ val incrementValue = ((ForwardSpeed.max - ForwardSpeed.value) / drivingCharacteristics.stepsToFullAcceleration)
+
+ acceleratorStepTask = AcceleratorStepTask(incrementValue)
+ acceleratorStepTask.schedule(accelerationPeriod)
+ }
+
+ MotionEvent.ACTION_UP -> {
+ offState()
+ acceleratorStepTask.cancel()
+ ForwardSpeed.setTo (drivingCharacteristics.minSpeed)
+ }
+ }
+ return true
+ }
+
+ inner class AcceleratorStepTask(incrementValue: Float) {
+ private var executor: ScheduledExecutorService = Executors.newScheduledThreadPool(1)
+ private lateinit var runningTask: ScheduledFuture<*>
+
+ private val task = Runnable {
+ ForwardSpeed.increment(incrementValue)
+ }
+
+ fun schedule(period: Long) {
+ this.runningTask = executor.scheduleAtFixedRate(task, 0, period, TimeUnit.MILLISECONDS)
+ }
+
+ fun cancel() {
+ if (this::runningTask.isInitialized && !runningTask.isCancelled) {
+ this.runningTask.cancel(false)
+ }
+ }
+ }
+}
diff --git a/android/controller/src/main/java/org/openbot/controller/customComponents/BotSetupControlsLayout.kt b/android/controller/src/main/java/org/openbot/controller/customComponents/BotSetupControlsLayout.kt
index dcab8cd84..62e9f7651 100644
--- a/android/controller/src/main/java/org/openbot/controller/customComponents/BotSetupControlsLayout.kt
+++ b/android/controller/src/main/java/org/openbot/controller/customComponents/BotSetupControlsLayout.kt
@@ -9,9 +9,11 @@
package org.openbot.controller.customComponents
+import android.annotation.SuppressLint
import android.content.Context
import android.util.AttributeSet
import android.widget.LinearLayout
+import org.openbot.controller.StatusEventBus
class BotSetupControlsLayout @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
diff --git a/android/controller/src/main/java/org/openbot/controller/customComponents/Brake.kt b/android/controller/src/main/java/org/openbot/controller/customComponents/Brake.kt
new file mode 100644
index 000000000..e135d30b0
--- /dev/null
+++ b/android/controller/src/main/java/org/openbot/controller/customComponents/Brake.kt
@@ -0,0 +1,91 @@
+/*
+ * Developed for the OpenBot project (https://openbot.org) by:
+ *
+ * Ivo Zivkov
+ * izivkov@gmail.com
+ *
+ * Date: 2020-12-27, 10:57 p.m.
+ */
+
+package org.openbot.controller.customComponents
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.MotionEvent
+import androidx.annotation.IntRange
+import org.openbot.controller.ForwardSpeed
+import java.util.concurrent.Executors
+import java.util.concurrent.ScheduledExecutorService
+import java.util.concurrent.ScheduledFuture
+import java.util.concurrent.TimeUnit
+import kotlin.math.absoluteValue
+
+class Brake @JvmOverloads constructor(
+ context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
+) : ButtonWithBorder(context, attrs, defStyleAttr) {
+
+ data class DrivingCharacteristics constructor(
+ @IntRange(
+ from = 0,
+ to = 5000
+ ) val timeToReverse: Long = 1000, // time before going into reverse.
+
+ @IntRange(
+ from = 1,
+ to = 10
+ ) val stepsToFinalValue: Int = 3, // in haw many steps do we get to min value
+ )
+
+ init {
+ offState()
+ }
+
+ private val drivingCharacteristics: DrivingCharacteristics = DrivingCharacteristics()
+ private lateinit var deceleratorStepTask: DeceleratorStepTask
+
+ override fun onTouchEvent(event: MotionEvent): Boolean {
+ when (event.action) {
+ MotionEvent.ACTION_DOWN -> {
+ onState()
+ ForwardSpeed.reset()
+ val decelerationPeriod =
+ drivingCharacteristics.timeToReverse / drivingCharacteristics.stepsToFinalValue
+ val decrementValue =
+ (ForwardSpeed.minNegative / drivingCharacteristics.stepsToFinalValue).absoluteValue
+
+ deceleratorStepTask = DeceleratorStepTask(decrementValue)
+ deceleratorStepTask.schedule(
+ drivingCharacteristics.timeToReverse,
+ decelerationPeriod
+ )
+ }
+
+ MotionEvent.ACTION_UP -> {
+ offState()
+ deceleratorStepTask.cancel()
+ ForwardSpeed.reset()
+ }
+ }
+ return true
+ }
+
+ inner class DeceleratorStepTask(decrementValue: Float) {
+ private var executor: ScheduledExecutorService = Executors.newScheduledThreadPool(1)
+ private lateinit var runningTask: ScheduledFuture<*>
+
+ private val task = Runnable {
+ ForwardSpeed.decrementNegative(decrementValue)
+ }
+
+ fun schedule(delay: Long, period: Long) {
+ this.runningTask =
+ executor.scheduleAtFixedRate(task, delay, period, TimeUnit.MILLISECONDS)
+ }
+
+ fun cancel() {
+ if (this::runningTask.isInitialized && !runningTask.isCancelled) {
+ this.runningTask.cancel(false)
+ }
+ }
+ }
+}
diff --git a/android/controller/src/main/java/org/openbot/controller/customComponents/Button.kt b/android/controller/src/main/java/org/openbot/controller/customComponents/Button.kt
index c9223db68..7899caef6 100644
--- a/android/controller/src/main/java/org/openbot/controller/customComponents/Button.kt
+++ b/android/controller/src/main/java/org/openbot/controller/customComponents/Button.kt
@@ -26,7 +26,7 @@ open class Button @JvmOverloads constructor(
init {
}
- fun show() {
+ open fun show() {
visibility = VISIBLE
}
@@ -57,7 +57,7 @@ open class Button @JvmOverloads constructor(
})
}
- protected fun setOnOffStateConditions(value: String) {
+ protected open fun setOnOffStateConditions(value: String) {
if (value == "true") onState() else offState()
}
diff --git a/android/controller/src/main/java/org/openbot/controller/customComponents/ButtonWithBorder.kt b/android/controller/src/main/java/org/openbot/controller/customComponents/ButtonWithBorder.kt
new file mode 100644
index 000000000..56786799d
--- /dev/null
+++ b/android/controller/src/main/java/org/openbot/controller/customComponents/ButtonWithBorder.kt
@@ -0,0 +1,41 @@
+/*
+ * Developed for the OpenBot project (https://openbot.org) by:
+ *
+ * Ivo Zivkov
+ * izivkov@gmail.com
+ *
+ * Date: 2020-12-27, 10:56 p.m.
+ */
+
+package org.openbot.controller.customComponents
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.content.res.ColorStateList
+import android.graphics.Color
+import android.graphics.drawable.Drawable
+import android.util.AttributeSet
+import android.view.MotionEvent
+import android.view.View
+import org.openbot.controller.ConnectionSelector
+import org.openbot.controller.StatusEventBus
+
+open class ButtonWithBorder @JvmOverloads constructor(
+ context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
+) : Button(context, attrs, defStyleAttr) {
+
+ init {
+ strokeWidth = 2
+ cornerRadius = 20
+ setTextColor(Color.rgb(0xf6, 0xf6, 0xf6)) // colorPrimary
+ strokeColor = ColorStateList.valueOf(Color.rgb(0xf6,0xf6,0xf6)) // colorPrimary
+ }
+
+ protected override fun offState() {
+ backgroundTintList = ColorStateList.valueOf(Color.rgb(0x40, 0x40, 0x41)) // colorPrimaryDark
+ }
+
+ protected override fun onState() {
+ backgroundTintList = ColorStateList.valueOf(Color.BLACK) // colorPrimary
+ }
+}
diff --git a/android/controller/src/main/java/org/openbot/controller/customComponents/ControllerModeSliders.kt b/android/controller/src/main/java/org/openbot/controller/customComponents/ControllerModeSliders.kt
new file mode 100644
index 000000000..9e8cb3de7
--- /dev/null
+++ b/android/controller/src/main/java/org/openbot/controller/customComponents/ControllerModeSliders.kt
@@ -0,0 +1,42 @@
+/*
+ * Developed for the OpenBot project (https://openbot.org) by:
+ *
+ * Ivo Zivkov
+ * izivkov@gmail.com
+ *
+ * Date: 2020-12-27, 10:57 p.m.
+ */
+
+package org.openbot.controller.customComponents
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.MotionEvent
+import org.openbot.controller.utils.LocalEventBus
+
+class ControllerModeSliders @JvmOverloads constructor(
+ context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
+) : ButtonWithBorder(context, attrs, defStyleAttr) {
+
+ init {
+ offState()
+ }
+
+ override fun onTouchEvent(event: MotionEvent): Boolean {
+ when (event.action) {
+ MotionEvent.ACTION_DOWN -> {
+ onState()
+
+ val event: LocalEventBus.ProgressEvents =
+ LocalEventBus.ProgressEvents.SlidersControl
+ LocalEventBus.onNext(event)
+ }
+
+ MotionEvent.ACTION_UP -> {
+ offState()
+ }
+ }
+ return true
+ }
+
+}
diff --git a/android/controller/src/main/java/org/openbot/controller/customComponents/ControllerModeTilt.kt b/android/controller/src/main/java/org/openbot/controller/customComponents/ControllerModeTilt.kt
new file mode 100644
index 000000000..cb8d2e484
--- /dev/null
+++ b/android/controller/src/main/java/org/openbot/controller/customComponents/ControllerModeTilt.kt
@@ -0,0 +1,43 @@
+/*
+ * Developed for the OpenBot project (https://openbot.org) by:
+ *
+ * Ivo Zivkov
+ * izivkov@gmail.com
+ *
+ * Date: 2020-12-27, 10:57 p.m.
+ */
+
+package org.openbot.controller.customComponents
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.MotionEvent
+import org.openbot.controller.utils.LocalEventBus
+import org.openbot.controller.utils.SensorReader
+
+class ControllerModeTilt @JvmOverloads constructor(
+ context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
+) : ButtonWithBorder(context, attrs, defStyleAttr) {
+
+ init {
+ SensorReader.init(context)
+ offState()
+ }
+
+ override fun onTouchEvent(event: MotionEvent): Boolean {
+ when (event.action) {
+ MotionEvent.ACTION_DOWN -> {
+ onState()
+
+ val event: LocalEventBus.ProgressEvents =
+ LocalEventBus.ProgressEvents.TiltControl
+ LocalEventBus.onNext(event)
+ }
+
+ MotionEvent.ACTION_UP -> {
+ offState()
+ }
+ }
+ return true
+ }
+}
diff --git a/android/controller/src/main/java/org/openbot/controller/customComponents/DriveModeControlsLayout.kt b/android/controller/src/main/java/org/openbot/controller/customComponents/DriveModeSlidersLayout.kt
similarity index 73%
rename from android/controller/src/main/java/org/openbot/controller/customComponents/DriveModeControlsLayout.kt
rename to android/controller/src/main/java/org/openbot/controller/customComponents/DriveModeSlidersLayout.kt
index 231de315b..b400c8bea 100644
--- a/android/controller/src/main/java/org/openbot/controller/customComponents/DriveModeControlsLayout.kt
+++ b/android/controller/src/main/java/org/openbot/controller/customComponents/DriveModeSlidersLayout.kt
@@ -9,19 +9,24 @@
package org.openbot.controller.customComponents
+import android.annotation.SuppressLint
import android.content.Context
import android.util.AttributeSet
import android.widget.RelativeLayout
+import org.openbot.controller.StatusEventBus
-class DriveModeControlsLayout @JvmOverloads constructor(
+class DriveModeSlidersLayout @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : RelativeLayout(context, attrs, defStyleAttr) {
+ init {
+ }
+
fun show() {
visibility = VISIBLE
}
fun hide() {
- visibility = INVISIBLE
+ visibility = GONE
}
}
diff --git a/android/controller/src/main/java/org/openbot/controller/customComponents/DriveModeTiltLayout.kt b/android/controller/src/main/java/org/openbot/controller/customComponents/DriveModeTiltLayout.kt
new file mode 100644
index 000000000..02cae7889
--- /dev/null
+++ b/android/controller/src/main/java/org/openbot/controller/customComponents/DriveModeTiltLayout.kt
@@ -0,0 +1,87 @@
+/*
+ * Developed for the OpenBot project (https://openbot.org) by:
+ *
+ * Ivo Zivkov
+ * izivkov@gmail.com
+ *
+ * Date: 2020-12-27, 10:57 p.m.
+ */
+
+package org.openbot.controller.customComponents
+
+import android.content.Context
+import android.util.AttributeSet
+import android.widget.RelativeLayout
+import org.openbot.controller.ConnectionSelector
+import org.openbot.controller.DriveCommandReducer
+import org.openbot.controller.PhoneSensorToDualDriveConverter
+import org.openbot.controller.utils.SensorReader
+import java.util.*
+
+class DriveModeTiltLayout @JvmOverloads constructor(
+ context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
+) : RelativeLayout(context, attrs, defStyleAttr) {
+
+ private val tag: String = "DriveModeTiltLayout"
+ private val sensorSampler: SensorSampler = SensorSampler()
+ private val g = 9.81f
+ private val phoneAccelerometerToDualDriveConverted =
+ PhoneSensorToDualDriveConverter()
+
+ init {
+ }
+
+ fun start() {
+ SensorReader.start()
+ sensorSampler.start()
+ show()
+ }
+
+ fun stop() {
+ sensorSampler.stop()
+ SensorReader.stop(context)
+ hide()
+ }
+
+ fun show() {
+ visibility = VISIBLE
+ }
+
+ fun hide() {
+ visibility = GONE
+ }
+
+ inner class SensorSampler {
+ private lateinit var timer: Timer
+
+ fun start() {
+ if (this::timer.isInitialized) {
+ timer.cancel()
+ }
+
+ timer = Timer()
+ var isRunning = false
+
+ val task = object : TimerTask() {
+ override fun run() {
+ val azimuth = SensorReader.values.azimuth
+ val pitch = SensorReader.values.pitch
+ val roll = SensorReader.values.roll
+
+ isRunning = true
+ val sliderValues = phoneAccelerometerToDualDriveConverted.convert(azimuth, pitch, roll)
+
+ DriveCommandReducer.filter(sliderValues.right, sliderValues.left)
+ }
+ }
+
+ timer.schedule(task, 0, 50 /*MS*/)
+ }
+
+ fun stop() {
+ if (this::timer.isInitialized) {
+ timer.cancel()
+ }
+ }
+ }
+}
diff --git a/android/controller/src/main/java/org/openbot/controller/customComponents/DualDriveSeekBar.kt b/android/controller/src/main/java/org/openbot/controller/customComponents/DualDriveSeekBar.kt
index 8b226cadf..774cfb819 100644
--- a/android/controller/src/main/java/org/openbot/controller/customComponents/DualDriveSeekBar.kt
+++ b/android/controller/src/main/java/org/openbot/controller/customComponents/DualDriveSeekBar.kt
@@ -12,7 +12,7 @@ import android.content.Context
import android.graphics.Canvas
import android.util.AttributeSet
import android.view.MotionEvent
-import org.openbot.controller.ConnectionSelector
+import org.openbot.controller.DriveCommandReducer
import java.util.concurrent.Executors
import java.util.concurrent.ScheduledExecutorService
import java.util.concurrent.ScheduledFuture
@@ -110,8 +110,7 @@ class DualDriveSeekBar @JvmOverloads constructor(
if ((System.currentTimeMillis() - lastTransmitted) >= MIN_TIME_BETWEEN_TRANSMISSIONS
|| lastRightValue == 0f || lastLeftValue == 0f) { // if home command, send, do not wait for a time lapsed.
- val msg = "{driveCmd: {r:$lastRightValue, l:$lastLeftValue}}"
- ConnectionSelector.getConnection().sendMessage(msg)
+ DriveCommandReducer.filter(lastRightValue, lastLeftValue)
lastTransmitted = System.currentTimeMillis()
}
}
@@ -121,7 +120,7 @@ class DualDriveSeekBar @JvmOverloads constructor(
var executor: ScheduledExecutorService = Executors.newScheduledThreadPool(1)
lateinit var runningTask: ScheduledFuture<*>
- val task = Runnable {
+ private val task = Runnable {
resetToHomePosition()
val safeValue = ((progress - 50) / 50f).coerceIn(-1f, 1f)
driveValue.invoke(safeValue)
diff --git a/android/controller/src/main/java/org/openbot/controller/customComponents/FadeableTextView.kt b/android/controller/src/main/java/org/openbot/controller/customComponents/FadeableTextView.kt
new file mode 100644
index 000000000..63ece0f09
--- /dev/null
+++ b/android/controller/src/main/java/org/openbot/controller/customComponents/FadeableTextView.kt
@@ -0,0 +1,57 @@
+/*
+ * Developed for the OpenBot project (https://openbot.org) by:
+ *
+ * Ivo Zivkov
+ * izivkov@gmail.com
+ *
+ * Date: 2020-12-27, 10:56 p.m.
+ */
+
+package org.openbot.controller.customComponents
+
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.animation.ObjectAnimator
+import android.content.Context
+import android.os.CountDownTimer
+import android.util.AttributeSet
+
+
+class FadeableTextView @JvmOverloads constructor(
+ context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
+) : com.google.android.material.textview.MaterialTextView(context, attrs, defStyleAttr) {
+
+ init {
+ show ()
+ }
+
+ fun start () {
+ object : CountDownTimer(3000, 1000) {
+ override fun onTick(millisUntilFinished: Long) {
+ }
+
+ override fun onFinish() {
+ fadeOut()
+ }
+ }.start()
+ }
+
+ fun show() {
+ visibility = VISIBLE
+ }
+
+ fun hide() {
+ visibility = INVISIBLE
+ }
+
+ fun fadeOut() {
+ val fadeOut = ObjectAnimator.ofFloat(this, "alpha", 1f, 0f)
+ fadeOut.duration = 500
+ fadeOut.addListener(object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator?) {
+ hide()
+ }
+ })
+ fadeOut.start()
+ }
+}
diff --git a/android/controller/src/main/java/org/openbot/controller/customComponents/LogsButton.kt b/android/controller/src/main/java/org/openbot/controller/customComponents/LogsButton.kt
index 3d4d577e7..266510007 100644
--- a/android/controller/src/main/java/org/openbot/controller/customComponents/LogsButton.kt
+++ b/android/controller/src/main/java/org/openbot/controller/customComponents/LogsButton.kt
@@ -19,6 +19,7 @@ class LogsButton @JvmOverloads constructor(
init {
setOnTouchListener(OnTouchListener("{command: LOGS}"))
subscribe("LOGS", ::onDataReceived)
+ offState()
}
private fun onDataReceived(data: String) {
diff --git a/android/controller/src/main/java/org/openbot/controller/customComponents/SplashScreenLayout.kt b/android/controller/src/main/java/org/openbot/controller/customComponents/SplashScreenLayout.kt
index f0c0b9cab..8ce4355e3 100644
--- a/android/controller/src/main/java/org/openbot/controller/customComponents/SplashScreenLayout.kt
+++ b/android/controller/src/main/java/org/openbot/controller/customComponents/SplashScreenLayout.kt
@@ -22,6 +22,7 @@ class SplashScreenLayout @JvmOverloads constructor(
fun show() {
visibility = VISIBLE
+ bringToFront()
}
fun hide() {
diff --git a/android/controller/src/main/java/org/openbot/controller/customComponents/VideoViewVlc.kt b/android/controller/src/main/java/org/openbot/controller/customComponents/VideoViewVlc.kt
index a6b799107..ec0861eba 100644
--- a/android/controller/src/main/java/org/openbot/controller/customComponents/VideoViewVlc.kt
+++ b/android/controller/src/main/java/org/openbot/controller/customComponents/VideoViewVlc.kt
@@ -13,10 +13,8 @@ import android.annotation.SuppressLint
import android.content.Context
import android.util.AttributeSet
import android.util.Log
-import io.reactivex.functions.Consumer
import org.openbot.controller.StatusEventBus
-import org.openbot.controller.databinding.ActivityFullscreenBinding
-import org.openbot.controller.utils.EventProcessor
+import org.openbot.controller.utils.LocalEventBus
import org.videolan.libvlc.interfaces.IVLCVout
@SuppressLint("CheckResult")
@@ -101,11 +99,11 @@ class VideoViewVlc @JvmOverloads constructor(
private fun monitorConnection() {
- EventProcessor.subscriber.start(
+ LocalEventBus.subscriber.start(
this.javaClass.simpleName,
{
when (it) {
- EventProcessor.ProgressEvents.Disconnected -> {
+ LocalEventBus.ProgressEvents.Disconnected -> {
stop()
}
}
diff --git a/android/controller/src/main/java/org/openbot/controller/customComponents/VideoViewWebRTC.kt b/android/controller/src/main/java/org/openbot/controller/customComponents/VideoViewWebRTC.kt
index 111ba5898..72ec0bfca 100644
--- a/android/controller/src/main/java/org/openbot/controller/customComponents/VideoViewWebRTC.kt
+++ b/android/controller/src/main/java/org/openbot/controller/customComponents/VideoViewWebRTC.kt
@@ -17,7 +17,7 @@ import org.json.JSONException
import org.json.JSONObject
import org.openbot.controller.ConnectionSelector
import org.openbot.controller.StatusEventBus
-import org.openbot.controller.utils.EventProcessor
+import org.openbot.controller.utils.LocalEventBus
import org.webrtc.*
/*
@@ -266,13 +266,13 @@ class VideoViewWebRTC @JvmOverloads constructor(
private fun monitorConnection() {
- EventProcessor.subscriber.start(
+ LocalEventBus.subscriber.start(
this.javaClass.simpleName,
{
Log.i(TAG, "Got $it event")
when (it) {
- EventProcessor.ProgressEvents.Disconnected -> {
+ LocalEventBus.ProgressEvents.Disconnected -> {
stop()
}
}
diff --git a/android/controller/src/main/java/org/openbot/controller/utils/EventProcessor.kt b/android/controller/src/main/java/org/openbot/controller/utils/LocalEventBus.kt
similarity index 92%
rename from android/controller/src/main/java/org/openbot/controller/utils/EventProcessor.kt
rename to android/controller/src/main/java/org/openbot/controller/utils/LocalEventBus.kt
index a48bdbc39..3c8f0f3f9 100644
--- a/android/controller/src/main/java/org/openbot/controller/utils/EventProcessor.kt
+++ b/android/controller/src/main/java/org/openbot/controller/utils/LocalEventBus.kt
@@ -17,7 +17,7 @@ import io.reactivex.functions.Consumer
import io.reactivex.processors.PublishProcessor
import java.util.*
-object EventProcessor {
+object LocalEventBus {
val subscriber = Subscriber()
@@ -63,5 +63,8 @@ object EventProcessor {
object StopAdvertising : ProgressEvents()
object AdvertisingFailed : ProgressEvents()
object TemporaryConnectionProblem : ProgressEvents()
+ object PhoneOnTable : ProgressEvents()
+ object TiltControl : ProgressEvents()
+ object SlidersControl : ProgressEvents()
}
}
\ No newline at end of file
diff --git a/android/controller/src/main/java/org/openbot/controller/utils/SensorReader.kt b/android/controller/src/main/java/org/openbot/controller/utils/SensorReader.kt
new file mode 100644
index 000000000..077df205c
--- /dev/null
+++ b/android/controller/src/main/java/org/openbot/controller/utils/SensorReader.kt
@@ -0,0 +1,78 @@
+package org.openbot.controller.utils
+
+import android.content.Context
+import android.content.Context.SENSOR_SERVICE
+import android.hardware.Sensor
+import android.hardware.SensorEvent
+import android.hardware.SensorEventListener
+import android.hardware.SensorManager
+import android.util.Log
+
+object SensorReader {
+ private var mSensorManager: SensorManager? = null
+ private var mSensorAccelerometer: Sensor? = null
+ private const val TAG: String = "SensorReader"
+ private var eventListener: SensorEventListener? = null
+
+ private const val g = 9.8f
+
+ class Values {
+ var azimuth: Float? = 0f
+ var pitch: Float? = 0f
+ var roll: Float? = 0f
+
+ fun reset () {
+ azimuth = 0f
+ pitch = 0f
+ roll = 0f
+ }
+ }
+ val values: Values = Values()
+
+ fun init(context: Context) {
+ mSensorManager = context.getSystemService(SENSOR_SERVICE) as SensorManager
+
+ mSensorAccelerometer = mSensorManager!!.getDefaultSensor(
+ Sensor.TYPE_ACCELEROMETER
+ )
+
+ eventListener = EventListener()
+ }
+
+ fun start() {
+ (eventListener as EventListener).start()
+ }
+
+ fun stop(context: Context) {
+ values.reset()
+ (eventListener as EventListener).stop(context)
+ }
+
+ class EventListener : SensorEventListener {
+ override fun onSensorChanged(event: SensorEvent?) {
+ values.azimuth = event?.values?.get(0)
+ values.pitch = event?.values?.get(1)
+ values.roll = event?.values?.get(2)
+ }
+
+ override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {
+ }
+
+ fun start() {
+
+ if (mSensorAccelerometer != null) {
+ mSensorManager?.registerListener(
+ this, mSensorAccelerometer,
+ SensorManager.SENSOR_DELAY_NORMAL
+ )
+ }
+ }
+
+ fun stop(context: Context) {
+
+ // Unregister all sensor listeners in this callback so they don't
+ // continue to use resources when the app is stopped.
+ mSensorManager?.unregisterListener(this)
+ }
+ }
+}
\ No newline at end of file
diff --git a/android/controller/src/main/java/org/openbot/controller/utils/Utils.kt b/android/controller/src/main/java/org/openbot/controller/utils/Utils.kt
index 43b887d69..df2bfba9f 100644
--- a/android/controller/src/main/java/org/openbot/controller/utils/Utils.kt
+++ b/android/controller/src/main/java/org/openbot/controller/utils/Utils.kt
@@ -64,4 +64,7 @@ object Utils {
return ""
}
+ fun isWithin(value: Float?, desiredValue: Float?, tolerance: Float?): Boolean {
+ return value!! in (desiredValue!! - tolerance!!)..(desiredValue + tolerance)
+ }
}
\ No newline at end of file
diff --git a/android/controller/src/main/res/drawable/control_mode.xml b/android/controller/src/main/res/drawable/control_mode.xml
new file mode 100644
index 000000000..2345caf2c
--- /dev/null
+++ b/android/controller/src/main/res/drawable/control_mode.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
diff --git a/android/controller/src/main/res/drawable/pedal_accelerator.xml b/android/controller/src/main/res/drawable/pedal_accelerator.xml
new file mode 100644
index 000000000..a42ba93f2
--- /dev/null
+++ b/android/controller/src/main/res/drawable/pedal_accelerator.xml
@@ -0,0 +1,4 @@
+
+
+
diff --git a/android/controller/src/main/res/drawable/pedal_break.xml b/android/controller/src/main/res/drawable/pedal_break.xml
new file mode 100644
index 000000000..64d1fd0c7
--- /dev/null
+++ b/android/controller/src/main/res/drawable/pedal_break.xml
@@ -0,0 +1,4 @@
+
+
+
diff --git a/android/controller/src/main/res/drawable/shade_dark.xml b/android/controller/src/main/res/drawable/shade_dark.xml
new file mode 100644
index 000000000..5d2d54572
--- /dev/null
+++ b/android/controller/src/main/res/drawable/shade_dark.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android/controller/src/main/res/drawable/zen_mode.xml b/android/controller/src/main/res/drawable/zen_mode.xml
new file mode 100644
index 000000000..2d8dd2de1
--- /dev/null
+++ b/android/controller/src/main/res/drawable/zen_mode.xml
@@ -0,0 +1,12 @@
+
+
+
+
diff --git a/android/controller/src/main/res/layout/activity_fullscreen.xml b/android/controller/src/main/res/layout/activity_fullscreen.xml
index 44f25b017..89396623c 100644
--- a/android/controller/src/main/res/layout/activity_fullscreen.xml
+++ b/android/controller/src/main/res/layout/activity_fullscreen.xml
@@ -70,8 +70,6 @@
-
-
@@ -129,8 +127,6 @@
android:layout_height="wrap_content"
android:layout_marginStart="0dp"
android:layout_marginEnd="20dp"
- android:layout_toStartOf="@+id/right_slider"
- android:layout_toEndOf="@+id/left_slider"
android:background="@drawable/shaded"
android:orientation="horizontal"
android:padding="5dp">
@@ -183,10 +179,10 @@
android:id="@+id/video_controls"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginTop="20dp"
+ android:layout_below="@id/top_buttons"
android:layout_marginStart="20dp"
+ android:layout_marginTop="20dp"
android:layout_toEndOf="@+id/left_slider"
- android:layout_below="@id/top_buttons"
android:orientation="vertical">
@@ -251,7 +247,65 @@
android:thumb="@drawable/custom_thumb" />
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+ android:orientation="vertical">
-
+ android:layout_height="match_parent"
+ android:gravity="center"
+ android:orientation="vertical">
-
+
+
+
+
+
+
+
diff --git a/android/controller/src/main/res/values/colors.xml b/android/controller/src/main/res/values/colors.xml
index 3b2c73b52..bae1e6ec8 100644
--- a/android/controller/src/main/res/values/colors.xml
+++ b/android/controller/src/main/res/values/colors.xml
@@ -8,4 +8,5 @@
#00ff00#ffffff#59000000
+ #80000000
\ No newline at end of file
diff --git a/android/controller/src/main/res/values/strings.xml b/android/controller/src/main/res/values/strings.xml
index 5afc34f80..18bdaa05f 100644
--- a/android/controller/src/main/res/values/strings.xml
+++ b/android/controller/src/main/res/values/strings.xml
@@ -10,8 +10,10 @@
Search againSearching for OpenBot…Select Phone as control mode in the bot app to connect
- Double-tap to switch screensLogsOpenBot IconApp Icon
+ Drive by tilting the phone
+ Use on-screen controls to drive
+ Double-tap to go back to previous screen.
\ No newline at end of file
diff --git a/android/controller/src/main/res/values/styles.xml b/android/controller/src/main/res/values/styles.xml
index c4cb5e807..5e622db80 100644
--- a/android/controller/src/main/res/values/styles.xml
+++ b/android/controller/src/main/res/values/styles.xml
@@ -1,4 +1,4 @@
-
+
+
+
+
+