Code for performing end-to-end latency test for inputs. Can be configured in two ways:
- 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.
- 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
- Esp-Latency-Test
- ESP32 dev board (code defaults to esp32), such as QtPy ESP32 PICO.
- 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
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.
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.
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
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"
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
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 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.
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
).
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
.
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 asXbox Wireless Controller
. You can simply set toXbox
parse_input
: Should be set totrue
input_report_id
: should be set to1
rp_byte0
: should be set to13
- this is the first byte index of the input report which corresponds to button data.rp_byte1
: should be set to14
- this is the second byte index of the input report which corresponds to button data.
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 asXbox Wireless Controller
. You can simply set toXbox
parse_input
: Should be set tofalse
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 asXbox Elite Wireless Controller
. You can simply set toXbox
parse_input
: Should be set tofalse
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 asWireless Controller
. You can simply set toWireless
if you want, or you can set it to"Wireless Controller"
- NOTE: the quotation marks are required.parse_input
: Should be set totrue
input_report_id
: should be set to1
rp_byte0
: should be set to4
- this is the first byte index of the input report which corresponds to button data.rp_byte1
: should be set to5
- this is the second byte index of the input report which corresponds to button data.
Info on its HID reports can be found here.
To pair this controller, simply press and hold the sync button while it is off.
device_name
: Should show up asPro Controller
. You can simply set toPro
if you want, or you can set it to"pro Controller"
- NOTE: the quotation marks are required.parse_input
: Should be set totrue
input_report_id
: should be set to63
rp_byte0
: should be set to0
- this is the first byte index of the input report which corresponds to button data.rp_byte1
: should be set to1
- this is the second byte index of the input report which corresponds to button data.
Info on its HID reports can be found here
Example screenshot of the console output from this app:
Example histogram generated from the analysis tool:
Example screenshot of the serial plotter running on the output of the app in real time:
Example screenshot analyzing multiple controllers (each with their own latency measurement files) at once: