aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorandroid-build-team Robot <android-build-team-robot@google.com>2017-05-17 14:21:58 +0000
committerandroid-build-team Robot <android-build-team-robot@google.com>2017-05-17 14:21:58 +0000
commit52926e018b1a1dad067d25ba0eb2be8dc15d49fd (patch)
treea30a23f07caa231d5014329963d8af7b07f41ab8
parented462e937cd99e9aaee98613c4e3e0469145d719 (diff)
parent2d3f8a1b548faefee8484ad5dcf5cfc698bcd443 (diff)
downloadwalt-oreo-m6-s3-release.tar.gz
release-request-323db86e-b638-4d24-8eb1-d2e3bf4a9d1a-for-git_oc-mr1-release-4017779 snap-temp-L47900000064949209android-wear-8.1.0_r1android-vts-8.1_r9android-vts-8.1_r8android-vts-8.1_r7android-vts-8.1_r6android-vts-8.1_r5android-vts-8.1_r4android-vts-8.1_r3android-vts-8.1_r14android-vts-8.1_r13android-vts-8.1_r12android-vts-8.1_r11android-vts-8.1_r10android-security-8.1.0_r93android-security-8.1.0_r92android-security-8.1.0_r91android-security-8.1.0_r90android-security-8.1.0_r89android-security-8.1.0_r88android-security-8.1.0_r87android-security-8.1.0_r86android-security-8.1.0_r85android-security-8.1.0_r84android-security-8.1.0_r83android-security-8.1.0_r82android-cts-8.1_r9android-cts-8.1_r8android-cts-8.1_r7android-cts-8.1_r6android-cts-8.1_r5android-cts-8.1_r4android-cts-8.1_r3android-cts-8.1_r25android-cts-8.1_r24android-cts-8.1_r23android-cts-8.1_r22android-cts-8.1_r21android-cts-8.1_r20android-cts-8.1_r2android-cts-8.1_r19android-cts-8.1_r18android-cts-8.1_r17android-cts-8.1_r16android-cts-8.1_r15android-cts-8.1_r14android-cts-8.1_r13android-cts-8.1_r12android-cts-8.1_r11android-cts-8.1_r10android-cts-8.1_r1android-8.1.0_r9android-8.1.0_r81android-8.1.0_r80android-8.1.0_r8android-8.1.0_r79android-8.1.0_r78android-8.1.0_r77android-8.1.0_r76android-8.1.0_r75android-8.1.0_r74android-8.1.0_r73android-8.1.0_r72android-8.1.0_r71android-8.1.0_r70android-8.1.0_r7android-8.1.0_r69android-8.1.0_r68android-8.1.0_r67android-8.1.0_r66android-8.1.0_r65android-8.1.0_r64android-8.1.0_r63android-8.1.0_r62android-8.1.0_r61android-8.1.0_r60android-8.1.0_r6android-8.1.0_r53android-8.1.0_r52android-8.1.0_r51android-8.1.0_r50android-8.1.0_r5android-8.1.0_r48android-8.1.0_r47android-8.1.0_r46android-8.1.0_r45android-8.1.0_r43android-8.1.0_r42android-8.1.0_r41android-8.1.0_r40android-8.1.0_r4android-8.1.0_r39android-8.1.0_r38android-8.1.0_r37android-8.1.0_r36android-8.1.0_r35android-8.1.0_r33android-8.1.0_r32android-8.1.0_r31android-8.1.0_r30android-8.1.0_r3android-8.1.0_r29android-8.1.0_r28android-8.1.0_r27android-8.1.0_r26android-8.1.0_r25android-8.1.0_r23android-8.1.0_r22android-8.1.0_r21android-8.1.0_r20android-8.1.0_r2android-8.1.0_r19android-8.1.0_r18android-8.1.0_r17android-8.1.0_r16android-8.1.0_r15android-8.1.0_r14android-8.1.0_r13android-8.1.0_r12android-8.1.0_r11android-8.1.0_r10android-8.1.0_r1security-oc-mr1-releaseoreo-mr1-wear-releaseoreo-mr1-vts-releaseoreo-mr1-security-releaseoreo-mr1-s1-releaseoreo-mr1-releaseoreo-mr1-cuttlefish-testingoreo-mr1-cts-releaseoreo-m8-releaseoreo-m7-releaseoreo-m6-s4-releaseoreo-m6-s3-releaseoreo-m6-s2-releaseoreo-m5-releaseoreo-m4-s9-releaseoreo-m4-s8-releaseoreo-m4-s7-releaseoreo-m4-s6-releaseoreo-m4-s5-releaseoreo-m4-s4-releaseoreo-m4-s3-releaseoreo-m4-s2-releaseoreo-m4-s12-releaseoreo-m4-s11-releaseoreo-m4-s10-releaseoreo-m4-s1-releaseoreo-m3-releaseoreo-m2-s5-releaseoreo-m2-s4-releaseoreo-m2-s3-releaseoreo-m2-s2-releaseoreo-m2-s1-releaseoreo-m2-release
Change-Id: Idf003230cc9df1881a0e63eb2d99a9e2d7bf8f0c
-rw-r--r--README.google91
-rw-r--r--android/WALT/app/src/main/java/org/chromium/latency/walt/DragLatencyFragment.java13
-rw-r--r--android/WALT/app/src/main/java/org/chromium/latency/walt/MainActivity.java93
-rw-r--r--android/WALT/app/src/main/java/org/chromium/latency/walt/RemoteClockInfo.java68
-rw-r--r--android/WALT/app/src/main/java/org/chromium/latency/walt/RobotAutomationListener.java27
-rw-r--r--android/WALT/app/src/main/java/org/chromium/latency/walt/ScreenResponseFragment.java22
-rw-r--r--android/WALT/app/src/main/java/org/chromium/latency/walt/TapLatencyFragment.java17
-rw-r--r--android/WALT/app/src/main/java/org/chromium/latency/walt/WaltDevice.java37
-rw-r--r--android/WALT/app/src/main/java/org/chromium/latency/walt/WaltTcpConnection.java46
-rw-r--r--arduino/walt/walt.ino9
-rw-r--r--pywalt/pywalt/minimization.py2
-rw-r--r--pywalt/pywalt/walt.py49
12 files changed, 357 insertions, 117 deletions
diff --git a/README.google b/README.google
new file mode 100644
index 0000000..fc3d2a0
--- /dev/null
+++ b/README.google
@@ -0,0 +1,91 @@
+Overview
+========
+
+This project is included in the internal Android source tree to support
+automated latency testing with Salad Fingers, a robot designed and built by
+Steve Pfetsch (spfetsch@). The three main components we use and modify are:
+
+ - the android app (android/)
+ - the WALT device firmware (arduino/)
+ - the TCP-to-serial bridge (pywalt/)
+
+Salad Fingers uses a single Teensyduino running WALT firmware to test multiple
+devices without human intervention. The devices under test are connected to the
+same host as the Teensy. An end-to-end connection for a single device (only one
+device can use the WALT at a time) looks like this:
+
+ Device ------ Host ------ Teensy
+ (android/) (pywalt/) (arduino/)
+
+For the device to communicate with the host over TCP on a physical USB
+connection, a "reverse" port has to be set up with adb. For example:
+
+ $ adb reverse tcp:50007 tcp:45454
+
+Any traffic the device sends to 127.0.0.1:50007 will come into the host on port
+45454 and vice versa. Port 50007 is defined in the app source, but the device
+port can be selected arbitrarily.
+
+The TCP-to-serial bridge runs on the host and connects the app's TCP pipe to
+the Teensy's serial pipe. However, there are two special commands the app can
+send to the bridge to synchronize the clocks between the device and the Teensy:
+"bridge sync" and "bridge update".
+
+This setup requires some modifications from the original source, which are
+explained in the next section, but behaves very similarly to a direct, Teensy-
+to-device USB connection.
+
+
+Modifications
+=============
+
+- Clock synchronization
+ Despite the reliability and accuracy of NTP, device and host wall clocks can
+ become significantly out of sync, especially if a device loses Wi-Fi
+ connectivity. To avoid this problem and take advantage of the low-latency
+ connection between the host and device, the clocks are synchronized based on
+ the time difference between when the bridge zeroed the Teensy's clock and
+ when the reply to the "bridge sync" command was sent to the device. This
+ required parallel changes in pywalt/ and android/.
+
+- Automation intents
+ The test scripts which control the robot (Salad Fingers) on which the Teensy
+ is mounted require the ability to control certain aspects of the app running
+ on a device. These are defined in a separate RobotAutomationEvent interface.
+ This required changes to android/.
+
+- Reverse port support
+ The WaltTcpConnection class was originally intended to communicate over a
+ true network from a Chromebook to a dedicated bridge with a specific IP
+ address. This address was changed to localhost in android/.
+
+- Strict networking and request ordering
+ To prevent crashes due to network accesses performed on the main thread,
+ which is disallowed in strict mode, all such accesses were moved to a
+ dedicated network thread. As a side benefit, all requests sent to the bridge
+ now wait for a response before the next request is sent. This complies with
+ the serial nature of the device and guarantees correct ordering. This
+ required changes to android/.
+
+- Hardware-specific firmware
+ The Teensy on Salad Fingers uses sensors that are not bundled with the
+ standard WALT hardware, so new thresholds for accelerometer shocks and photo-
+ diode readings were required to achieve accurate results. This required
+ changes to arduino/.
+
+
+Usage
+=====
+
+This project is not intended to be automatically built. Instead, as needed, the
+app will be built manually and checked in as a prebuilt that PTS will pick up.
+The firmware cannot be built or loaded automatically, as it requires a modded
+version of the Arduino project for Teensy. The required software can be loaded
+on a laptop to flash the Teensy mounted on Salad Fingers.
+
+
+See Also
+========
+
+Project metadata: vendor/google_meta/platform/external/walt/
+PTS integration: vendor/google_testing/pts/tests/salad_fingers/
diff --git a/android/WALT/app/src/main/java/org/chromium/latency/walt/DragLatencyFragment.java b/android/WALT/app/src/main/java/org/chromium/latency/walt/DragLatencyFragment.java
index 109fcf8..af03e36 100644
--- a/android/WALT/app/src/main/java/org/chromium/latency/walt/DragLatencyFragment.java
+++ b/android/WALT/app/src/main/java/org/chromium/latency/walt/DragLatencyFragment.java
@@ -39,7 +39,8 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.Locale;
-public class DragLatencyFragment extends Fragment implements View.OnClickListener {
+public class DragLatencyFragment extends Fragment
+ implements View.OnClickListener, RobotAutomationListener {
private SimpleLogger logger;
private WaltDevice waltDevice;
@@ -332,6 +333,16 @@ public class DragLatencyFragment extends Fragment implements View.OnClickListene
}
}
+ public void onRobotAutomationEvent(String event) {
+ if (event.equals(RobotAutomationListener.RESTART_EVENT)) {
+ onClick(restartButton);
+ } else if (event.equals(RobotAutomationListener.START_EVENT)) {
+ onClick(startButton);
+ } else if (event.equals(RobotAutomationListener.FINISH_EVENT)) {
+ onClick(finishButton);
+ }
+ }
+
private WaltDevice.TriggerHandler triggerHandler = new WaltDevice.TriggerHandler() {
@Override
public void onReceive(WaltDevice.TriggerMessage tmsg) {
diff --git a/android/WALT/app/src/main/java/org/chromium/latency/walt/MainActivity.java b/android/WALT/app/src/main/java/org/chromium/latency/walt/MainActivity.java
index 7efee00..ac1df47 100644
--- a/android/WALT/app/src/main/java/org/chromium/latency/walt/MainActivity.java
+++ b/android/WALT/app/src/main/java/org/chromium/latency/walt/MainActivity.java
@@ -65,6 +65,10 @@ public class MainActivity extends AppCompatActivity {
private static final String TAG = "WALT";
private static final int PERMISSION_REQUEST_WRITE_EXTERNAL_STORAGE_SHARE_LOG = 2;
private static final int PERMISSION_REQUEST_WRITE_EXTERNAL_STORAGE_SYSTRACE = 3;
+ private static final int PERMISSION_REQUEST_WRITE_EXTERNAL_STORAGE_WRITE_LOG = 4;
+ private static final int PERMISSION_REQUEST_WRITE_EXTERNAL_STORAGE_CLEAR_LOG = 5;
+
+ private static final String LOG_FILENAME = "qstep_log.txt";
private Toolbar toolbar;
LocalBroadcastManager broadcastManager;
@@ -74,6 +78,8 @@ public class MainActivity extends AppCompatActivity {
public Handler handler = new Handler();
+ private Fragment mRobotAutomationFragment;
+
/**
* A method to display exceptions on screen. This is very useful because our USB port is taken
@@ -132,6 +138,41 @@ public class MainActivity extends AppCompatActivity {
autoRunFragment.setArguments(intent.getExtras());
switchScreen(autoRunFragment, "Automated Test");
}
+
+ // Handle robot automation originating from adb shell am
+ if (intent != null && Intent.ACTION_SEND.equals(intent.getAction())) {
+ Log.e(TAG, "Received Intent: " + intent.toString());
+ String test = intent.getStringExtra("StartTest");
+ if (test != null) {
+ Log.e(TAG, "Extras \"StartTest\" = " + test);
+ if ("TapLatencyTest".equals(test)) {
+ mRobotAutomationFragment = new TapLatencyFragment();
+ switchScreen(mRobotAutomationFragment, "Tap Latency");
+ } else if ("ScreenResponseTest".equals(test)) {
+ mRobotAutomationFragment = new ScreenResponseFragment();
+ switchScreen(mRobotAutomationFragment, "Screen Response");
+ } else if ("DragLatencyTest".equals(test)) {
+ mRobotAutomationFragment = new DragLatencyFragment();
+ switchScreen(mRobotAutomationFragment, "Drag Latency");
+ }
+ }
+
+ String robotEvent = intent.getStringExtra("RobotAutomationEvent");
+ if (robotEvent != null && mRobotAutomationFragment != null) {
+ Log.e(TAG, "Received robot automation event=\"" + robotEvent + "\", Fragment = " +
+ mRobotAutomationFragment);
+ // Writing and clearing the log is not fragment-specific, so handle them here.
+ if (robotEvent.equals(RobotAutomationListener.WRITE_LOG_EVENT)) {
+ attemptSaveLog();
+ } else if (robotEvent.equals(RobotAutomationListener.CLEAR_LOG_EVENT)) {
+ attemptClearLog();
+ } else {
+ // All other robot automation events are forwarded to the current fragment.
+ ((RobotAutomationListener) mRobotAutomationFragment)
+ .onRobotAutomationEvent(robotEvent);
+ }
+ }
+ }
}
@Override
@@ -389,6 +430,30 @@ public class MainActivity extends AppCompatActivity {
}
}
+ private void attemptSaveLog() {
+ int currentPermission = ContextCompat.checkSelfPermission(this,
+ Manifest.permission.WRITE_EXTERNAL_STORAGE);
+ if (currentPermission == PackageManager.PERMISSION_GRANTED) {
+ saveLogToFile();
+ } else {
+ ActivityCompat.requestPermissions(this,
+ new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
+ PERMISSION_REQUEST_WRITE_EXTERNAL_STORAGE_WRITE_LOG);
+ }
+ }
+
+ private void attemptClearLog() {
+ int currentPermission = ContextCompat.checkSelfPermission(this,
+ Manifest.permission.WRITE_EXTERNAL_STORAGE);
+ if (currentPermission == PackageManager.PERMISSION_GRANTED) {
+ clearLogFile();
+ } else {
+ ActivityCompat.requestPermissions(this,
+ new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
+ PERMISSION_REQUEST_WRITE_EXTERNAL_STORAGE_CLEAR_LOG);
+ }
+ }
+
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
@@ -401,6 +466,12 @@ public class MainActivity extends AppCompatActivity {
case PERMISSION_REQUEST_WRITE_EXTERNAL_STORAGE_SHARE_LOG:
attemptSaveAndShareLog();
break;
+ case PERMISSION_REQUEST_WRITE_EXTERNAL_STORAGE_WRITE_LOG:
+ attemptSaveLog();
+ break;
+ case PERMISSION_REQUEST_WRITE_EXTERNAL_STORAGE_CLEAR_LOG:
+ attemptClearLog();
+ break;
}
}
@@ -414,22 +485,19 @@ public class MainActivity extends AppCompatActivity {
// is frowned upon, but deliberately giving permissions as part of the intent is
// way too cumbersome.
- String fname = "qstep_log.txt";
// A reasonable world readable location,on many phones it's /storage/emulated/Documents
// TODO: make this location configurable?
File path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS);
File file = null;
FileOutputStream outStream = null;
- Date now = new Date();
- logger.log("Saving log to:\n" + path.getPath() + "/" + fname);
- logger.log("On: " + now.toString());
-
try {
if (!path.exists()) {
path.mkdirs();
}
- file = new File(path, fname);
+ file = new File(path, LOG_FILENAME);
+ logger.log("Saving log to: " + file + " at " + new Date());
+
outStream = new FileOutputStream(file);
outStream.write(logger.getLogText().getBytes());
@@ -437,11 +505,22 @@ public class MainActivity extends AppCompatActivity {
logger.log("Log saved");
} catch (Exception e) {
e.printStackTrace();
- logger.log("Exception:\n" + e.getMessage());
+ logger.log("Failed to write log: " + e.getMessage());
}
return file.getPath();
}
+ public void clearLogFile() {
+ File path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS);
+ try {
+ File file = new File(path, LOG_FILENAME);
+ file.delete();
+ } catch (Exception e) {
+ e.printStackTrace();
+ logger.log("Failed to clear log: " + e.getMessage());
+ }
+ }
+
public void shareLogFile(String filepath) {
File file = new File(filepath);
logger.log("Firing Intent.ACTION_SEND for file:");
diff --git a/android/WALT/app/src/main/java/org/chromium/latency/walt/RemoteClockInfo.java b/android/WALT/app/src/main/java/org/chromium/latency/walt/RemoteClockInfo.java
index 1a42eb5..4db9358 100644
--- a/android/WALT/app/src/main/java/org/chromium/latency/walt/RemoteClockInfo.java
+++ b/android/WALT/app/src/main/java/org/chromium/latency/walt/RemoteClockInfo.java
@@ -16,10 +16,6 @@
package org.chromium.latency.walt;
-import android.util.Log;
-
-import java.lang.reflect.Method;
-
/**
* Representation of our best knowledge of the remote clock.
* All time variables here are stored in microseconds.
@@ -27,18 +23,18 @@ import java.lang.reflect.Method;
* Which time reporting function is used locally on Android:
* This app uses SystemClock.uptimeMillis() for keeping local time which, up to
* units, is the same time reported by System.nanoTime() and by
- * clock_gettime(CLOCK_MONOTONIC, &ts) from time.h and is, roughly, the time
+ * clock_gettime(CLOCK_MONOTONIC, &amp;ts) from time.h and is, roughly, the time
* elapsed since last boot, excluding sleep time.
*
- * base_time is the local Android time when remote clock was zeroed.
+ * base_time is the local monotonic clock time when remote clock was zeroed.
*
* micros() is our best available approximation of the current reading of the remote clock.
*
- * Immediately after synchronization minLag is set to zero and the remote clock guaranteed to lag
- * behind what micros() reports by at most maxLag.
+ * Immediately after synchronization, minLag is set to zero and the remote clock is guaranteed to
+ * lag behind what micros() reports by at most maxLag.
*
* Immediately after synchronization or an update of the bounds (minLag, maxLag) the following holds
- * t_remote + minLag < micros() < t_rmote + maxLag
+ * t_remote + minLag &lt; micros() &lt; t_remote + maxLag
*
* For more details about clock synchronization refer to
* https://github.com/google/walt/blob/master/android/WALT/app/src/main/jni/README.md
@@ -50,67 +46,19 @@ public class RemoteClockInfo {
public int maxLag;
public long baseTime;
-
- public long micros() {
- return microTime() - baseTime;
- }
-
public static long microTime() {
return System.nanoTime() / 1000;
}
-
- /**
- Find the wall time when uptime was zero = CLOCK_REALTIME - CLOCK_MONOTONIC
-
- Needed for TCP bridge because Python prior to 3.3 has no direct access to CLOCK_MONOTONIC
- so the bridge returns timestamps as wall time and we need to convert them to CLOCK_MONOTONIC.
-
- See:
- [1] https://docs.python.org/3/library/time.html#time.CLOCK_MONOTONIC
- [2] http://stackoverflow.com/questions/14270300/what-is-the-difference-between-clock-monotonic-clock-monotonic-raw
- [3] http://stackoverflow.com/questions/1205722/how-do-i-get-monotonic-time-durations-in-python
-
- android.os.SystemClock.currentTimeMicros() is hidden by @hide which means it can't be called
- directly - calling it via reflection.
-
- See:
- http://stackoverflow.com/questions/17035271/what-does-hide-mean-in-the-android-source-code
- */
- public static long uptimeZero() {
- long t = -1;
- long dt = Long.MAX_VALUE;
- try {
- Class cls = Class.forName("android.os.SystemClock");
- Method myTimeGetter = cls.getMethod("currentTimeMicro");
- t = (long) myTimeGetter.invoke(null);
- dt = t - microTime();
- } catch (Exception e) {
- Log.i("WALT.uptimeZero", e.getMessage());
- }
-
- return dt;
- }
-
- public static long currentTimeMicro() {
-
- long t = -1;
- try {
- Class cls = Class.forName("android.os.SystemClock");
- Method myTimeGetter = cls.getMethod("currentTimeMicro");
- t = (long) myTimeGetter.invoke(null);
- } catch (Exception e) {
- Log.i("WALT.currentTimeMicro", e.getMessage());
- }
-
- return t;
+ public long micros() {
+ return microTime() - baseTime;
}
public int getMeanLag() {
return (minLag + maxLag) / 2;
}
- public String toString(){
+ public String toString() {
return "Remote clock [us]: current time = " + micros() + " baseTime = " + baseTime +
" lagBounds = (" + minLag + ", " + maxLag + ")";
}
diff --git a/android/WALT/app/src/main/java/org/chromium/latency/walt/RobotAutomationListener.java b/android/WALT/app/src/main/java/org/chromium/latency/walt/RobotAutomationListener.java
new file mode 100644
index 0000000..47a21ba
--- /dev/null
+++ b/android/WALT/app/src/main/java/org/chromium/latency/walt/RobotAutomationListener.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.chromium.latency.walt;
+
+public interface RobotAutomationListener {
+ public static final String START_EVENT = "START_EVENT";
+ public static final String RESTART_EVENT = "RESTART_EVENT";
+ public static final String FINISH_EVENT = "FINISH_EVENT";
+ public static final String WRITE_LOG_EVENT = "WRITE_LOG_EVENT";
+ public static final String CLEAR_LOG_EVENT = "CLEAR_LOG_EVENT";
+
+ public void onRobotAutomationEvent(String event);
+}
diff --git a/android/WALT/app/src/main/java/org/chromium/latency/walt/ScreenResponseFragment.java b/android/WALT/app/src/main/java/org/chromium/latency/walt/ScreenResponseFragment.java
index cfe6a53..629ed7d 100644
--- a/android/WALT/app/src/main/java/org/chromium/latency/walt/ScreenResponseFragment.java
+++ b/android/WALT/app/src/main/java/org/chromium/latency/walt/ScreenResponseFragment.java
@@ -53,7 +53,8 @@ import static org.chromium.latency.walt.Utils.getIntPreference;
/**
* Measurement of screen response time when switching between black and white.
*/
-public class ScreenResponseFragment extends Fragment implements View.OnClickListener {
+public class ScreenResponseFragment extends Fragment
+ implements View.OnClickListener, RobotAutomationListener {
private static final int CURVE_TIMEOUT = 1000; // milliseconds
private static final int CURVE_BLINK_TIME = 250; // milliseconds
@@ -248,14 +249,13 @@ public class ScreenResponseFragment extends Fragment implements View.OnClickList
Choreographer.getInstance().postFrameCallback(new Choreographer.FrameCallback() {
@Override
public void doFrame(long frameTimeNanos) {
- // frameTimeNanos is he time in nanoseconds when the frame started being
+ // frameTimeNanos is the time in nanoseconds when the frame started being
// rendered, in the nanoTime() timebase.
lastFrameStartTime = frameTimeNanos / 1000 - waltDevice.clock.baseTime;
lastFrameCallbackTime = System.nanoTime() / 1000 - waltDevice.clock.baseTime;
}
});
-
// Repost doBlink to some far away time to blink again even if nothing arrives from
// Teensy. This callback will almost always get cancelled by onIncomingTimestamp()
handler.postDelayed(doBlinkRunnable, 550 + (long) (Math.random()*100));
@@ -290,9 +290,9 @@ public class ScreenResponseFragment extends Fragment implements View.OnClickList
}
}
- final long startTimeMicros = lastFrameStartTime + waltDevice.clock.baseTime;
- final long finishTimeMicros = tmsg.t + waltDevice.clock.baseTime;
if (traceLogger != null) {
+ final long startTimeMicros = lastFrameStartTime + waltDevice.clock.baseTime;
+ final long finishTimeMicros = tmsg.t + waltDevice.clock.baseTime;
traceLogger.log(startTimeMicros, finishTimeMicros,
isBoxWhite ? "Black-to-white" : "White-to-black",
"Bar starts at beginning of frame and ends when photosensor detects blink");
@@ -423,6 +423,18 @@ public class ScreenResponseFragment extends Fragment implements View.OnClickList
}
}
+ public void onRobotAutomationEvent(String event) {
+ // Never show the latency chart during automated runs.
+ shouldShowLatencyChart = false;
+ // Disable full-screen mode to prevent modal user dialog.
+ enableFullScreen = false;
+ if (event.equals(RobotAutomationListener.RESTART_EVENT)) {
+ onClick(stopButton);
+ } else if (event.equals(RobotAutomationListener.START_EVENT)) {
+ onClick(startButton);
+ }
+ }
+
private WaltDevice.TriggerHandler brightnessTriggerHandler = new WaltDevice.TriggerHandler() {
@Override
public void onReceive(WaltDevice.TriggerMessage tmsg) {
diff --git a/android/WALT/app/src/main/java/org/chromium/latency/walt/TapLatencyFragment.java b/android/WALT/app/src/main/java/org/chromium/latency/walt/TapLatencyFragment.java
index 64e333d..e26a328 100644
--- a/android/WALT/app/src/main/java/org/chromium/latency/walt/TapLatencyFragment.java
+++ b/android/WALT/app/src/main/java/org/chromium/latency/walt/TapLatencyFragment.java
@@ -36,7 +36,7 @@ import java.util.Locale;
import static org.chromium.latency.walt.Utils.getBooleanPreference;
public class TapLatencyFragment extends Fragment
- implements View.OnClickListener {
+ implements View.OnClickListener, RobotAutomationListener {
private static final int ACTION_DOWN_INDEX = 0;
private static final int ACTION_UP_INDEX = 1;
@@ -193,8 +193,8 @@ public class TapLatencyFragment extends Fragment
}
if (dt < 0 || dt > 200) {
- logger.log(action + " bogus kernelTime, ignored, dt=" + dt);
- return false;
+ logger.log(action + " bogus kernelTime=" + e.kernelTime + ", ignored, dt=" + dt);
+ return false;
}
return true;
}
@@ -303,4 +303,15 @@ public class TapLatencyFragment extends Fragment
}
}
+
+ public void onRobotAutomationEvent(String event) {
+ // Never show the latency chart during automated runs.
+ shouldShowLatencyChart = false;
+ if (event.equals(RobotAutomationListener.RESTART_EVENT) ||
+ event.equals(RobotAutomationListener.START_EVENT)) {
+ restartMeasurement();
+ } else if (event.equals(RobotAutomationListener.FINISH_EVENT)) {
+ finishAndShowStats();
+ }
+ }
}
diff --git a/android/WALT/app/src/main/java/org/chromium/latency/walt/WaltDevice.java b/android/WALT/app/src/main/java/org/chromium/latency/walt/WaltDevice.java
index 96ddcfd..8ec2cb4 100644
--- a/android/WALT/app/src/main/java/org/chromium/latency/walt/WaltDevice.java
+++ b/android/WALT/app/src/main/java/org/chromium/latency/walt/WaltDevice.java
@@ -156,16 +156,20 @@ public class WaltDevice implements WaltConnection.ConnectionStateListener {
private String sendReceive(char c) throws IOException {
- connection.sendByte(c);
- return readOne();
+ synchronized (connection) {
+ connection.sendByte(c);
+ return readOne();
+ }
}
public void sendAndFlush(char c) {
try {
- connection.sendByte(c);
- while(connection.blockingRead(buffer) > 0) {
- // flushing all incoming data
+ synchronized (connection) {
+ connection.sendByte(c);
+ while (connection.blockingRead(buffer) > 0) {
+ // flushing all incoming data
+ }
}
} catch (Exception e) {
logger.log("Exception in sendAndFlush: " + e.getMessage());
@@ -187,6 +191,7 @@ public class WaltDevice implements WaltConnection.ConnectionStateListener {
throw new IOException("Unexpected response from WALT. Expected \"" + ack
+ "\", got \"" + response + "\"");
}
+ // Trim out the ack
return response.substring(1).trim();
}
@@ -237,6 +242,10 @@ public class WaltDevice implements WaltConnection.ConnectionStateListener {
return;
}
connection.updateLag();
+ if (clock == null) {
+ // updateLag() will have logged a message if we get here
+ return;
+ }
int drift = Math.abs(clock.getMeanLag());
String msg = String.format("Remote clock delayed between %d and %d us",
clock.minLag, clock.maxLag);
@@ -321,11 +330,13 @@ public class WaltDevice implements WaltConnection.ConnectionStateListener {
}
void onReceiveRaw(String s) {
- if (TriggerMessage.isTriggerString(s)) {
- TriggerMessage tmsg = new TriggerMessage(s.substring(1).trim());
- onReceive(tmsg);
- } else {
- Log.i(TAG, "Malformed trigger data: " + s);
+ for (String trigger : s.split("\n")) {
+ if (TriggerMessage.isTriggerString(trigger)) {
+ TriggerMessage tmsg = new TriggerMessage(trigger.substring(1).trim());
+ onReceive(tmsg);
+ } else {
+ Log.i(TAG, "Malformed trigger data: " + s);
+ }
}
}
@@ -391,6 +402,12 @@ public class WaltDevice implements WaltConnection.ConnectionStateListener {
}
public void stopListener() {
+ // If the trigger listener is already stopped, then it is possible the listener thread is
+ // null. In that case, calling stop() followed by join() will result in a listener object
+ // that is stuck in the STOPPING state.
+ if (triggerListener.isStopped()) {
+ return;
+ }
logger.log("Stopping Listener");
triggerListener.stop();
try {
diff --git a/android/WALT/app/src/main/java/org/chromium/latency/walt/WaltTcpConnection.java b/android/WALT/app/src/main/java/org/chromium/latency/walt/WaltTcpConnection.java
index ee9c143..e63f4dc 100644
--- a/android/WALT/app/src/main/java/org/chromium/latency/walt/WaltTcpConnection.java
+++ b/android/WALT/app/src/main/java/org/chromium/latency/walt/WaltTcpConnection.java
@@ -32,8 +32,8 @@ import java.net.SocketTimeoutException;
public class WaltTcpConnection implements WaltConnection {
- // The local ip on ARC++ to connect to underlying ChromeOS
- private static final String SERVER_IP = "192.168.254.1";
+ // Use a "reverse" port over adb. The server is running on the host to which we're attached.
+ private static final String SERVER_IP = "127.0.0.1";
private static final int SERVER_PORT = 50007;
private static final int TCP_READ_TIMEOUT_MS = 200;
@@ -74,6 +74,10 @@ public class WaltTcpConnection implements WaltConnection {
}
public void connect() {
+ // If the singleton is already connected, do not kill the connection.
+ if (isConnected()) {
+ return;
+ }
connectionState = Utils.ListenerState.STARTING;
networkThread = new HandlerThread("NetworkThread");
networkThread.start();
@@ -85,6 +89,7 @@ public class WaltTcpConnection implements WaltConnection {
try {
InetAddress serverAddr = InetAddress.getByName(SERVER_IP);
socket = new Socket(serverAddr, SERVER_PORT);
+ socket.setKeepAlive(true);
socket.setSoTimeout(TCP_READ_TIMEOUT_MS);
outputStream = socket.getOutputStream();
inputStream = socket.getInputStream();
@@ -119,18 +124,39 @@ public class WaltTcpConnection implements WaltConnection {
return connectionState == Utils.ListenerState.RUNNING;
}
- public void sendByte(char c) throws IOException {
- outputStream.write(Utils.char2byte(c));
+ public void sendByte(final char c) throws IOException {
+ // All network accesses must occur on a separate thread.
+ networkHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ outputStream.write(Utils.char2byte(c));
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ });
}
- public void sendString(String s) throws IOException {
- outputStream.write(s.getBytes("UTF-8"));
+ public void sendString(final String s) throws IOException {
+ // All network accesses must occur on a separate thread.
+ networkHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ outputStream.write(s.getBytes("UTF-8"));
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ });
}
public synchronized int blockingRead(byte[] buff) {
messageReceived = false;
+ // All network accesses must occur on a separate thread.
networkHandler.post(new Runnable() {
@Override
public void run() {
@@ -171,8 +197,7 @@ public class WaltTcpConnection implements WaltConnection {
return lastRetVal;
}
-
- private void updateClock(String cmd) throws IOException {
+ private synchronized void updateClock(String cmd) throws IOException {
sendString(cmd);
int retval = blockingRead(buffer);
if (retval <= 0) {
@@ -181,8 +206,9 @@ public class WaltTcpConnection implements WaltConnection {
String s = new String(buffer, 0, retval);
String[] parts = s.trim().split("\\s+");
// TODO: make sure reply starts with "clock"
- long wallBaseTime = Long.parseLong(parts[1]);
- remoteClock.baseTime = wallBaseTime - RemoteClockInfo.uptimeZero();
+ // The bridge sends the time difference between when it sent the reply and when it zeroed
+ // the WALT's clock. We assume here that the reply transit time is negligible.
+ remoteClock.baseTime = RemoteClockInfo.microTime() - Long.parseLong(parts[1]);
remoteClock.minLag = Integer.parseInt(parts[2]);
remoteClock.maxLag = Integer.parseInt(parts[3]);
}
diff --git a/arduino/walt/walt.ino b/arduino/walt/walt.ino
index e63da3b..ddb7c95 100644
--- a/arduino/walt/walt.ino
+++ b/arduino/walt/walt.ino
@@ -68,8 +68,11 @@
#define MIC_PIN 23 // Same as A9
// Threshold and hysteresis for screen on/off reading
-#define SCREEN_THRESH_HIGH 110
-#define SCREEN_THRESH_LOW 90
+#define SCREEN_THRESH_HIGH 800
+#define SCREEN_THRESH_LOW 300
+
+// Shock threshold
+#define GSHOCK_THRESHOLD 500
elapsedMicros time_us;
@@ -378,7 +381,7 @@ void loop() {
// Probe the accelerometer
if (gshock.probe) {
int v = analogRead(G_PIN);
- if (v > 900) {
+ if (v > GSHOCK_THRESHOLD) {
gshock.t = time_us;
gshock.count++;
gshock.probe = false;
diff --git a/pywalt/pywalt/minimization.py b/pywalt/pywalt/minimization.py
index 1337587..dc5c0be 100644
--- a/pywalt/pywalt/minimization.py
+++ b/pywalt/pywalt/minimization.py
@@ -21,7 +21,7 @@ QuickStep laser crossing timestamps
"""
import numpy
-from . import evparser
+import evparser
debug_mode = False
diff --git a/pywalt/pywalt/walt.py b/pywalt/pywalt/walt.py
index 2dbcf8b..9987f4f 100644
--- a/pywalt/pywalt/walt.py
+++ b/pywalt/pywalt/walt.py
@@ -48,9 +48,9 @@ import time
import serial
import numpy
-from . import evparser
-from . import minimization
-from . import screen_stats
+import evparser
+import minimization
+import screen_stats
# Time units
@@ -620,14 +620,31 @@ class TcpServer:
# Discard any empty data
if not data or len(data) == 0:
print('o<: discarded empty data')
- return None
+ return
# Get a string version of the data for checking longer commands
s = data.decode(self.walt.encoding)
- if s.startswith('bridge'):
- log('bridge command: %s, pausing ser2net thread...' % s)
+ bridge_command = None
+ while len(s) > 0:
+ if not bridge_command:
+ bridge_command = re.search(r'bridge (sync|update)', s)
+ # If a "bridge" command does not exist, send everything to the WALT
+ if not bridge_command:
+ self.walt.ser.write(s.encode(self.walt.encoding))
+ break
+ # If a "bridge" command is preceded by WALT commands, send those
+ # first
+ if bridge_command.start() > 0:
+ before_command = s[:bridge_command.start()]
+ log('found bridge command after "%s"' % before_command)
+ s = s[bridge_command.start():]
+ self.walt.ser.write(before_command.encode(self.walt.encoding))
+ continue
+ # Otherwise, reply directly to the command
+ log('bridge command: %s, pausing ser2net thread...' %
+ bridge_command.group(0))
self.pause()
- is_sync = 'sync' in s
+ is_sync = bridge_command.group(1) == 'sync' or not self.walt.base_time
if is_sync:
self.walt.zero_clock()
@@ -638,16 +655,16 @@ class TcpServer:
self.walt.max_lag -= self.walt.min_lag
self.walt.min_lag = 0
- t0 = self.walt.base_time * 1e6
min_lag = self.walt.min_lag * 1e6
max_lag = self.walt.max_lag * 1e6
- reply = 'clock %d %d %d\n' % (t0, min_lag, max_lag)
- print('|custom-reply>: ' + repr(reply))
+ # Send the time difference between now and when the clock was zeroed
+ dt0 = (time.time() - self.walt.base_time) * 1e6
+ reply = 'clock %d %d %d\n' % (dt0, min_lag, max_lag)
self.net.sendall(reply)
+ print('|custom-reply>: ' + repr(reply))
self.resume()
- return None
-
- return data
+ s = s[bridge_command.end():]
+ bridge_command = None
def connections_loop(self):
with contextlib.closing(socket.socket(
@@ -655,6 +672,7 @@ class TcpServer:
self.sock = sock
# SO_REUSEADDR is supposed to prevent the "Address already in use" error
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+ sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
sock.bind((self.host, self.port))
sock.listen(1)
while True:
@@ -677,10 +695,7 @@ class TcpServer:
data = self.net.recv(1024)
if not data:
break # got disconnected
-
- data = self.net2ser(data)
- if(data):
- self.walt.ser.write(data)
+ self.net2ser(data)
def ser2net_loop(self):
while True: