diff options
author | android-build-team Robot <android-build-team-robot@google.com> | 2017-05-17 14:21:58 +0000 |
---|---|---|
committer | android-build-team Robot <android-build-team-robot@google.com> | 2017-05-17 14:21:58 +0000 |
commit | 52926e018b1a1dad067d25ba0eb2be8dc15d49fd (patch) | |
tree | a30a23f07caa231d5014329963d8af7b07f41ab8 | |
parent | ed462e937cd99e9aaee98613c4e3e0469145d719 (diff) | |
parent | 2d3f8a1b548faefee8484ad5dcf5cfc698bcd443 (diff) | |
download | walt-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
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, &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 < micros() < 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: |