/esp-latency-test

BT Classic / BLE Gamepad latency test app to actuate a button from an ESP32 and measure time until either 1) photodiode registers screen change, or 2) the ESP32 receives an input report. Can also work with USB gamepads in non-hosted (using phone screen) mode.

Primary LanguageCMIT LicenseMIT

Esp-Latency-Test

Code for performing end-to-end latency test for inputs. Can be configured in two ways:

  1. ADC Measurement for end to end with a phone: actuate the button, then use a photo-diode/photo-transistor to measure when the screen changes and computes the time it took.
  2. Hosted measurement for end to end with the ESP: actuate the button, then measure the time it takes to receive the updated input report.

See also esp-usb-latency-test

This repository also contains a couple python analysis tools:

  • analysis.py can be used to plot a histogram of latency values that are measured from the system.
  • multi-analysis.py can be used to analyze multiple controller analysis files simultaneously, plotting them in a single box-plot of latency vs battery life according to a meta-config file provided.

Table of Contents

Hardware Needed

  1. ESP32 dev board (code defaults to esp32), such as QtPy ESP32 PICO.
  2. Dupont wires to connect to button on controller (patch into button and gnd signal).

For measurement method (1/ADC) above, you'll also need: 3. Photo-diode for measuring the brightness / light of the screen. I used Amazon 3mm flat head PhotoDiode. 4. Resistor (1k-10k) from photodiode output to ground.

If you're planning to run method (1/ADC) above, you'll likely need to run the embedded code once with CONFIG_DEBUG_PLOT_ALL enabled (via menuconfig), so that you can see the ADC values for the screen on/off state based on the screen / app / sensor you select and how you've mounted them. I use duct tape to "mount" the sensor to my phone screen 😅. Then you can configure the appropriate upper/lower thresholds accordingly to take data.

Some controllers, such as

  • Xbox Elite Wireless Controller 2 (model 1797)
  • Xbox Wireless Controller (model 1708)
  • Playstation Dualsense (model CFI-SCT1W)
  • Nintendo Switch Pro Controller

Use

It's recommended to use the uart_serial_plotter after flashing to monitor and plot the latency values in real time. If you do this, you can then also save the resultant output to a text file.

This text file can be loaded and parsed by the analysis.py script and the multi-analysis.py script.

Real-time Plotting

You can use the uart_serial_plotter to plot the latency values in real time.

# follow setup / use instructions in esp-cpp/uart_serial_plotter repo
➜  uart_serial_plotter git:(master) $ source env/bin/activate
(env) ➜  uart_serial_plotter git:(master) $ python src/main.py

It will automatically find and open the serial port with the esp32 attached. If there are multiple, you can use the Serial menu. to select another port.

If you want to save the recorded data to a file, you can use File > Export UART Data command to save the data to a txt file.

Analysis

Setup

These setup steps only need to be run the first time you set up the python environment.

# create the environment
➜  esp-latency-test git:(main) $ python3 -m venv env

# activate the environment
➜  esp-latency-test git:(main) $ source env/bin/activate

# install the dependencies (matplotlib, numpy)
(env) ➜  esp-latency-test git:(main) $ pip install -r requirements.txt

Running

Any time you have a text file of csv data (such as what comes from the esp32 code), you can run the python script on it to generate a histogram.

# This will run an interactive plot with matplotlib
(env) ➜  esp-latency-test git:(main) $ python ./analysis.py tests/2024-05-30.txt

# This will simply save the output to the provided png file (destination folder must exist if provided)
(env) ➜  esp-latency-test git:(main) $ python ./analysis.py tests/2024-05-30.txt --output output/2024-05-30.png

# you can also specify your own title
(env) ➜  esp-latency-test git:(main) $ python ./analysis.py tests/2024-05-30-15ms-wake.txt --output output/2024-05-30-15ms-wake.png --title "Latency Histogram"

You can also run the multi-analysis.py script to analyze multiple controller latency files at once. This script will generate a box plot of the latency values for each controller, and a scatter plot of latency vs battery life.

# this will simply load in the files listed in the meta-config (csv) file and plot them all
(env) ➜  esp-latency-test git:(main) $ python ./multi-analysis.py hosted.csv

# you can optionally provide a title for the plot
(env) ➜  esp-latency-test git:(main) $ python ./multi-analysis.py hosted.csv --title "Latency vs Battery Life"

Cloning

Since this repo contains a submodule, you need to make sure you clone it recursively, e.g. with:

git clone --recurse-submodules git@github.com:finger563/esp-latency-test

Alternatively, you can always ensure the submodules are up to date after cloning (or if you forgot to clone recursively) by running:

git submodule update --init --recursive

Build Configuration

You can configure a few parts of the project, such as the GPIO for the button, the ADC for the sensor, and the thresholds to be used.

To configure the project, run

idf.py menuconfig

You can configure:

  • DEBUG_PLOT_ALL: enables a more verbose output which plots the raw adc value and some other state variables every 5ms.
  • BUTTON_HOLD_TIME_MS: how long the button should be held down for when it is pressed.
  • UPPER_THRESHOLD: the value that the ADC must rise above before the screen is sensed as on / button is sensed as pressed.
  • LOWER_THRESHOLD: the value that the ADC must drop below before the screen is sensed as off / button is sensed as released. BUTTON_GPIO to trigger a button press event.
  • BUTTON_GPIO: the gpio on the board which is connected to the controller button.
  • EXTRA_GND_GPIO: the gpio on the board which should output a logic low / gnd signal. Useful to connect to controller ground.
  • SENSOR_ADC_UNIT: The ADC Unit of the ESP the photodiode is connected to.
  • SENSOR_ADC_CHANNEL: The ADC channel of the ESP the photodiode is connected to.

Build and Flash

Build the project and flash it to the board, then run monitor tool to view serial output:

idf.py -p PORT flash monitor

(Replace PORT with the name of the serial port to use.)

(To exit the serial monitor, type Ctrl-].)

See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.

Runtime Configuration

The test has a CLI which allows you to switch between method (1/ADC), and method (2/Hosted) measurements, and allows you to configure some parameters of the test such as which value represents a button press, what device to connect to, etc. For a full list of options, run the help command.

The configuration is loaded from NVS flash and printed on boot (and can be printed from the CLI by typing config).

Pairing and configuring your controller

When booting, the tester device waits for input for 3 seconds. It will print a message and during this period if you press any key on the keyboard via the serial terminal, it will go into the command line interface (CLI).

In this CLI, you can edit the runtime configuration mentioned above. Below, I've written down some valid configurations for various controllers.

You'll want to put the controller into pairing mode and run the scan command. Afterwards (assuming the device under test (DUT) shows up in the scan result list), you'll want to set the device_name and other settings accordingly. Some examples are given below. You can then connect then / or exit.

Xbox Wireless Controller (1914)

To pair this controller, press and hold the xbox button to turn on the controller, then press and hold the sync button until the xbox light starts flashing quickly.

  • device_name: Should show up as Xbox Wireless Controller. You can simply set to Xbox
  • parse_input: Should be set to true
  • input_report_id: should be set to 1
  • rp_byte0: should be set to 13 - this is the first byte index of the input report which corresponds to button data.
  • rp_byte1: should be set to 14 - this is the second byte index of the input report which corresponds to button data.

Xbox Wireless Controller (1708)

To pair this controller, press and hold the xbox button to turn on the controller, then press and hold the sync button until the xbox light starts flashing quickly.

  • device_name: Should show up as Xbox Wireless Controller. You can simply set to Xbox
  • parse_input: Should be set to false

Xbox Elite Wireless Controller 2 (1797)

To pair this controller, press and hold the xbox button to turn on the controller, then press and hold the sync button until the xbox light starts flashing quickly.

  • device_name: Should show up as Xbox Elite Wireless Controller. You can simply set to Xbox
  • parse_input: Should be set to false

Playstation Dualsense (model CFI-SCT1W)

To pair this controller, press and hold the share button (left) and then press and release the playstation button. You'll know it's in pairing mode if the led is flashing very quickly.

  • device_name: Should show up as Wireless Controller. You can simply set to Wireless if you want, or you can set it to "Wireless Controller" - NOTE: the quotation marks are required.
  • parse_input: Should be set to true
  • input_report_id: should be set to 1
  • rp_byte0: should be set to 4 - this is the first byte index of the input report which corresponds to button data.
  • rp_byte1: should be set to 5 - this is the second byte index of the input report which corresponds to button data.

Info on its HID reports can be found here.

Nintendo Switch Pro Controller

To pair this controller, simply press and hold the sync button while it is off.

  • device_name: Should show up as Pro Controller. You can simply set to Pro if you want, or you can set it to "pro Controller" - NOTE: the quotation marks are required.
  • parse_input: Should be set to true
  • input_report_id: should be set to 63
  • rp_byte0: should be set to 0 - this is the first byte index of the input report which corresponds to button data.
  • rp_byte1: should be set to 1 - this is the second byte index of the input report which corresponds to button data.

Info on its HID reports can be found here

Output

Example screenshot of the console output from this app:

CleanShot 2024-06-05 at 16 45 56

Example histogram generated from the analysis tool:

image

Example screenshot of the serial plotter running on the output of the app in real time:

image

Example screenshot analyzing multiple controllers (each with their own latency measurement files) at once:

CleanShot 2024-06-24 at 09 39 59