Support running cp-caps and OTCI using a host POSIX ot-cli instance connected to a Radio URL (e.g. spinel+hdlc+unix://) or via automated Android adb Spinel proxy port forwarding. This enables running the latest host-built OpenThread diagnostics and capability tests against co-processors without relying on on-device ot-ctl or older Android system binaries.
Co-Processor Capability Test Suite (cp-caps)
The Co-Processor Capability Test Suite (cp-caps) checks which capabilities an OpenThread co-processor (currently an RCP) supports, such as diag commands, IEEE 802.15.4 frame formats, CSL, data poll, and link metrics. It also measures performance metrics, such as Thread network 1-hop throughput.
Unlike a conformance or certification test suite, it profiles the co-processor: each test case reports whether a capability is supported (OK or NotSupported) or a measured value, rather than an overall pass or fail.
The test suite consists of:
- Test cases (
test_*.py): each test case tests a capability area and is run using Python'sunittestframework. - A test harness (
device_manager.py): connects to and controls the testbed devices using OTCI and outputs the results. - A testbed: the devices the test cases run on (see Testbed).
Testbed
+-------+
+---------------| PC |----------------+
| +-------+ |
| ADB/SSH | | ADB/SSH/SERIAL
| | SERIAL |
+-------+ | +------------------+
| DUT |<-----------Thread-------->| Reference Device |
+-------+ | +------------------+
+-----------+
| Sniffer | (optional)
+-----------+
- PC : The computer to run the test script.
- DUT : The device under test.
- Reference Device : The device that supports all tested features.
- Sniffer : An RCP that captures frames with timestamps (e.g. to check the timing of frames). It is optional and only used by some test cases.
Reference Device
The nRF52840DK is set as the reference device by default. Testers can also select the other Thread device as the reference device.
Sniffer
The sniffer is an RCP (e.g. an nRF52840 dongle running ot-rcp) driven by ot-extcap from ot-br-posix. The test cases capture frames by running ot-extcap on the channel of interest, and read each frame with its timestamp in microseconds taken by the sniffer radio.
Quick guide to building ot-extcap:
$ git clone [email protected]:openthread/ot-br-posix.git
$ cd ot-br-posix/
$ git submodule update --init
$ ./script/cmake-build extcap
The executable is at build/extcap/ot-extcap. Set OT_EXTCAP to its full path (unless it is in the PATH), and SNIFFER_SERIAL (or SNIFFER_RADIO_URL) to the sniffer RCP.
Quick guide to setting up the nRF52840DK:
$ git clone [email protected]:openthread/ot-nrf528xx.git
$ cd ot-nrf528xx/
$ git submodule update --init
$ ./script/bootstrap
$ ./script/build nrf52840 UART_trans -DOT_DIAGNOSTIC=ON -DOT_CSL_RECEIVER=ON -DOT_LINK_METRICS_INITIATOR=ON -DOT_LINK_METRICS_SUBJECT=ON -DOT_WAKE_INITIATOR=ON
$ arm-none-eabi-objcopy -O ihex build/bin/ot-cli-ftd ot-cli-ftd.hex
$ nrfutil device program --firmware ot-cli-ftd.hex --options chip_erase_mode=ERASE_ALL
$ nrfutil device reset --reset-kind=RESET_PIN
Python Virtual Environment Setup
It is recommended to use a Python virtual environment to manage project dependencies and avoid conflicts with system-wide packages.
Creation
Create a virtual environment named .venv in the project directory:
$ cd openthread
$ python3 -m venv .venv
Activation
Activate the virtual environment using the following command:
$ source .venv/bin/activate
After activation, your shell prompt should indicate that you are working within the virtual environment.
Dependency Installation
Once the virtual environment is activated, install the required dependencies using pip:
$ pip3 install -r ./tools/cp-caps/requirements.txt ./tools/otci
Deactivation
When you are finished working in the virtual environment, you can deactivate it by running the following command. This will return you to your system's global Python environment.
$ deactivate
Test Commands
Help Info
usage: [device configurations] python3 -m unittest -q [test cases]
This test suite is used for testing the capabilities of the RCP.
Device configurations:
ADB_KEY=<adb_key> Full path to the adb key
DEBUG=on Enable the debug information
DUT_ADB_TCP=<device_ip> Connect to the DUT via adb tcp
DUT_ADB_USB=<serial_number> Connect to the DUT via adb usb
DUT_ADB_SPINEL_PROXY=<serial> Connect to the DUT Android device via adb Spinel proxy (host ot-cli)
DUT_SPINEL_PROXY=1 Enable adb Spinel proxy mode for DUT_ADB_USB/DUT_ADB_TCP (host ot-cli)
DUT_SPINEL_PROXY_SOCKET=<path> Socket path for adb Spinel proxy forwarding
DUT_RADIO_URL=<radio_url> Connect to the DUT via host POSIX ot-cli over radio URL
DUT_CLI_SERIAL=<serial_device> Connect to the DUT via cli serial port
DUT_SSH=<device_ip> Connect to the DUT via ssh
REF_ADB_TCP=<device_ip> Connect to the reference device via adb tcp
REF_ADB_USB=<serial_number> Connect to the reference device via adb usb
REF_ADB_SPINEL_PROXY=<serial> Connect to the reference Android device via adb Spinel proxy (host ot-cli)
REF_SPINEL_PROXY=1 Enable adb Spinel proxy mode for REF_ADB_USB/REF_ADB_TCP (host ot-cli)
REF_SPINEL_PROXY_SOCKET=<path> Socket path for reference device adb Spinel proxy forwarding
REF_RADIO_URL=<radio_url> Connect to the reference device via host POSIX ot-cli over radio URL
REF_CLI_SERIAL=<serial_device> Connect to the reference device via cli serial port
REF_SSH=<device_ip> Connect to the reference device via ssh
OT_CLI=<ot_cli_path> Full path to the host POSIX ot-cli executable (default: ot-cli)
SNIFFER_SERIAL=<serial_device> Use the RCP at the serial port as the sniffer (optional)
SNIFFER_RADIO_URL=<radio_url> Use the RCP at the radio URL as the sniffer (optional)
OT_EXTCAP=<ot_extcap_path> Full path to the ot-extcap executable used by the sniffer (default: ot-extcap)
Test cases:
test_csl test whether the RCP supports CSL transmitter
test_data_poll test whether the RCP supports data poll
test_diag_commands test whether the RCP supports all diag commands
test_frame_formats test whether the RCP supports 802.15.4 frames of all formats
test_link_metrics test whether the RCP supports link metrics
test_radio_frame_tx_info test mTxInfo field of the radio frame
test_throughput test Thread network 1-hop throughput
Examples:
Run test cases of the specified categories:
DUT_ADB_USB=1169UC2F2T0M95OR REF_CLI_SERIAL=/dev/ttyACM0 python3 -m unittest -q test_csl test_data_poll
Run test cases with host POSIX ot-cli over Radio URL (e.g. Unix socket):
DUT_RADIO_URL="spinel+hdlc+unix:///tmp/ot_spinel_proxy" REF_CLI_SERIAL=/dev/ttyACM0 python3 -m unittest -q test_diag_commands
Run test cases with automated Android adb Spinel proxy setup:
DUT_ADB_SPINEL_PROXY=1169UC2F2T0M95OR REF_CLI_SERIAL=/dev/ttyACM0 python3 -m unittest -q test_diag_commands
Run all test cases:
DUT_ADB_USB=1169UC2F2T0M95OR REF_CLI_SERIAL=/dev/ttyACM0 python3 -m unittest discover -q -s ./ -p 'test_*.py'
Run specified test case:
DUT_ADB_USB=1169UC2F2T0M95OR REF_CLI_SERIAL=/dev/ttyACM0 python3 -m unittest -q test_diag_commands.TestDiagCommands.test_diag_channel
These commands should be run in the path `tools/cp-caps`.
Note: If you get an error of
LIBUSB_ERROR_BUSYwhen you are using the ADB USB interface, please run the commandadb kill-serverto kill the ADB server.
Test Diag Commands
The test case test_diag_commands tests all diag commands.
Following environment variables are used to configure diag command parameters:
- DUT_DIAG_GPIO: Diag gpio value. The default value is
0if it is not set. - DUT_DIAG_RAW_POWER_SETTING: Diag raw power setting value. The default value is
112233if it is not set. - DUT_DIAG_POWER: Diag power value. The default value is
10if it is not set.
tools/cp-caps$ DUT_ADB_USB=1269UCKFZTAM95OR REF_CLI_SERIAL=/dev/ttyACM0 DUT_DIAG_GPIO=2 DUT_DIAG_RAW_POWER_SETTING=44556688 DUT_DIAG_POWER=11 python3 -m unittest -q test_diag_commands
diag channel --------------------------------------------- OK
diag channel 20 ------------------------------------------ OK
diag cw start -------------------------------------------- OK
diag cw stop --------------------------------------------- OK
diag echo 0123456789 ------------------------------------- OK
diag echo -n 10 ------------------------------------------ OK
diag frame 00010203040506070809 -------------------------- OK
diag gpio mode 2 ----------------------------------------- OK
diag gpio mode 2 in -------------------------------------- OK
diag gpio mode 2 out ------------------------------------- OK
diag gpio get 2 ------------------------------------------ OK
diag gpio set 2 0 ---------------------------------------- OK
diag gpio set 2 1 ---------------------------------------- OK
diag power ----------------------------------------------- OK
diag power 11 -------------------------------------------- OK
diag powersettings --------------------------------------- NotSupported
diag powersettings 20 ------------------------------------ NotSupported
diag radio receive --------------------------------------- OK
diag radio sleep ----------------------------------------- OK
diag radio state ----------------------------------------- OK
diag rawpowersetting enable ------------------------------ NotSupported
diag rawpowersetting 44556688 ---------------------------- NotSupported
diag rawpowersetting ------------------------------------- NotSupported
diag rawpowersetting disable ----------------------------- NotSupported
diag repeat 10 64 ---------------------------------------- OK
diag repeat stop ----------------------------------------- OK
diag send 100 64 ----------------------------------------- OK
diag stats ----------------------------------------------- OK
diag stats clear ----------------------------------------- OK
diag stream start ---------------------------------------- NotSupported
diag stream stop ----------------------------------------- NotSupported
Test CSL Transmitter
The test case test_csl tests whether the RCP supports the CSL transmitter.
tools/cp-caps$ DUT_ADB_USB=TW69UCKFZTGM95OR REF_CLI_SERIAL=/dev/ttyACM0 python3 -m unittest -q test_csl
CSL Transmitter ------------------------------------------ OK
Test Data Poll
The test case test_data_poll tests whether the RCP supports data poll.
tools/cp-caps$ DUT_ADB_USB=TW69UCKFZTGM95OR REF_CLI_SERIAL=/dev/ttyACM0 python3 -m unittest -q test_data_poll
Data Poll Child ------------------------------------------ OK
Data Poll Parent ----------------------------------------- OK
Test Link Metrics
The test case test_link_metrics tests whether the RCP supports link metrics.
tools/cp-caps$ DUT_ADB_USB=TW69UCKFZTGM95OR REF_CLI_SERIAL=/dev/ttyACM0 python3 -m unittest -q test_link_metrics
Link Metrics Initiator ----------------------------------- OK
Link Metrics Subject ------------------------------------- OK
Test Throughput
The test case test_throughput tests the Thread network 1-hop throughput of the DUT.
tools/cp-caps$ DUT_ADB_USB=TW69UCKFZTGM95OR REF_ADB_USB=44061HFAG01AQK python3 -m unittest -q test_throughput
Throughput ----------------------------------------------- 75.6 Kbits/sec
Test Frame Format
The test case test_frame_formats tests whether the RCP supports sending and receiving 802.15.4 frames of all formats.
tools/cp-caps$ DUT_ADB_USB=TW69UCKFZTGM95OR REF_CLI_SERIAL=/dev/ttyACM0 python3 -m unittest -q test_frame_formats
1 TX ver:2003,Cmd,seq,dst[addr:short,pan:id],src[addr:no,pan:no],sec:no,ie:no,plen:0 --------------- OK
1 RX ver:2003,Cmd,seq,dst[addr:short,pan:id],src[addr:no,pan:no],sec:no,ie:no,plen:0 --------------- OK
2 TX ver:2003,Bcon,seq,dst[addr:no,pan:no],src[addr:extd,pan:id],sec:no,ie:no,plen:30 -------------- OK
2 RX ver:2003,Bcon,seq,dst[addr:no,pan:no],src[addr:extd,pan:id],sec:no,ie:no,plen:30 -------------- OK
3 TX ver:2003,MP,noseq,dst[addr:extd,pan:id],src[addr:extd,pan:no],sec:l5,ie[ren con],plen:0 ------- OK
3 RX ver:2003,MP,noseq,dst[addr:extd,pan:id],src[addr:extd,pan:no],sec:l5,ie[ren con],plen:0 ------- OK
4 TX ver:2006,Cmd,seq,dst[addr:short,pan:id],src[addr:short,pan:no],sec:l5,ie:no,plen:0 ------------ OK
4 RX ver:2006,Cmd,seq,dst[addr:short,pan:id],src[addr:short,pan:no],sec:l5,ie:no,plen:0 ------------ OK
5 TX ver:2006,Cmd,seq,dst[addr:extd,pan:id],src[addr:extd,pan:no],sec:l5,ie:no,plen:0 -------------- OK
5 RX ver:2006,Cmd,seq,dst[addr:extd,pan:id],src[addr:extd,pan:no],sec:l5,ie:no,plen:0 -------------- OK
6 TX ver:2006,Data,seq,dst[addr:extd,pan:id],src[addr:extd,pan:id],sec:no,ie:no,plen:0 ------------- OK
6 RX ver:2006,Data,seq,dst[addr:extd,pan:id],src[addr:extd,pan:id],sec:no,ie:no,plen:0 ------------- OK
7 TX ver:2006,Data,seq,dst[addr:short,pan:id],src[addr:short,pan:id],sec:no,ie:no,plen:0 ----------- OK
7 RX ver:2006,Data,seq,dst[addr:short,pan:id],src[addr:short,pan:id],sec:no,ie:no,plen:0 ----------- OK
8 TX ver:2006,Data,seq,dst[addr:extd,pan:id],src[addr:no,pan:no],sec:no,ie:no,plen:0 --------------- OK
8 RX ver:2006,Data,seq,dst[addr:extd,pan:id],src[addr:no,pan:no],sec:no,ie:no,plen:0 --------------- OK
9 TX ver:2006,Data,seq,dst[addr:short,pan:id],src[addr:no,pan:no],sec:no,ie:no,plen:0 -------------- OK
9 RX ver:2006,Data,seq,dst[addr:short,pan:id],src[addr:no,pan:no],sec:no,ie:no,plen:0 -------------- OK
10 TX ver:2015,Data,seq,dst[addr:no,pan:no],src[addr:no,pan:no],sec:no,ie:no,plen:0 ---------------- OK
10 RX ver:2015,Data,seq,dst[addr:no,pan:no],src[addr:no,pan:no],sec:no,ie:no,plen:0 ---------------- OK
11 TX ver:2015,Data,seq,dst[addr:no,pan:id],src[addr:no,pan:no],sec:no,ie:no,plen:0 ---------------- OK
11 RX ver:2015,Data,seq,dst[addr:no,pan:id],src[addr:no,pan:no],sec:no,ie:no,plen:0 ---------------- OK
12 TX ver:2015,Data,seq,dst[addr:extd,pan:id],src[addr:no,pan:no],sec:no,ie:no,plen:0 -------------- OK
12 RX ver:2015,Data,seq,dst[addr:extd,pan:id],src[addr:no,pan:no],sec:no,ie:no,plen:0 -------------- OK
13 TX ver:2015,Data,seq,dst[addr:extd,pan:no],src[addr:no,pan:no],sec:no,ie:no,plen:0 -------------- OK
13 RX ver:2015,Data,seq,dst[addr:extd,pan:no],src[addr:no,pan:no],sec:no,ie:no,plen:0 -------------- OK
14 TX ver:2015,Data,seq,dst[addr:no,pan:no],src[addr:extd,pan:id],sec:no,ie:no,plen:0 -------------- OK
14 RX ver:2015,Data,seq,dst[addr:no,pan:no],src[addr:extd,pan:id],sec:no,ie:no,plen:0 -------------- OK
15 TX ver:2015,Data,seq,dst[addr:no,pan:no],src[addr:extd,pan:no],sec:no,ie:no,plen:0 -------------- OK
15 RX ver:2015,Data,seq,dst[addr:no,pan:no],src[addr:extd,pan:no],sec:no,ie:no,plen:0 -------------- OK
16 TX ver:2015,Data,seq,dst[addr:extd,pan:id],src[addr:extd,pan:no],sec:no,ie:no,plen:0 ------------ OK
16 RX ver:2015,Data,seq,dst[addr:extd,pan:id],src[addr:extd,pan:no],sec:no,ie:no,plen:0 ------------ OK
17 TX ver:2015,Data,seq,dst[addr:extd,pan:no],src[addr:extd,pan:no],sec:no,ie:no,plen:0 ------------ OK
17 RX ver:2015,Data,seq,dst[addr:extd,pan:no],src[addr:extd,pan:no],sec:no,ie:no,plen:0 ------------ OK
18 TX ver:2015,Data,seq,dst[addr:short,pan:id],src[addr:short,pan:id],sec:no,ie:no,plen:0 ---------- OK
18 RX ver:2015,Data,seq,dst[addr:short,pan:id],src[addr:short,pan:id],sec:no,ie:no,plen:0 ---------- OK
19 TX ver:2015,Data,seq,dst[addr:short,pan:id],src[addr:extd,pan:id],sec:no,ie:no,plen:0 ----------- OK
19 RX ver:2015,Data,seq,dst[addr:short,pan:id],src[addr:extd,pan:id],sec:no,ie:no,plen:0 ----------- OK
20 TX ver:2015,Data,seq,dst[addr:extd,pan:id],src[addr:short,pan:id],sec:no,ie:no,plen:0 ----------- OK
20 RX ver:2015,Data,seq,dst[addr:extd,pan:id],src[addr:short,pan:id],sec:no,ie:no,plen:0 ----------- OK
21 TX ver:2015,Data,seq,dst[addr:short,pan:id],src[addr:short,pan:id],sec:no,ie[csl],plen:0 -------- OK
21 RX ver:2015,Data,seq,dst[addr:short,pan:id],src[addr:short,pan:id],sec:no,ie[csl],plen:0 -------- OK
22 TX ver:2015,Data,noseq,dst[addr:short,pan:id],src[addr:short,pan:id],sec:no,ie:no,plen:0 -------- OK
22 RX ver:2015,Data,noseq,dst[addr:short,pan:id],src[addr:short,pan:id],sec:no,ie:no,plen:0 -------- OK
23 TX ver:2015,Data,seq,dst[addr:extd,pan:id],src[addr:extd,pan:no],sec:l5,keyid:2,ie:no,plen:0 ---- OK
23 RX ver:2015,Data,seq,dst[addr:extd,pan:id],src[addr:extd,pan:no],sec:l5,keyid:2,ie:no,plen:0 ---- OK
Test mTxInfo field of the radio frame.
The test case test_radio_frame_tx_info tests whether the RCP supports the mTxInfo field of the radio frame.
tools/cp-caps$ DUT_ADB_USB=TW69UCKFZTGM95OR REF_CLI_SERIAL=/dev/ttyACM0 python3 -m unittest -q test_radio_frame_tx_info
mCsmaCaEnabled=0 ----------------------------------------- OK
mCsmaCaEnabled=1 ----------------------------------------- OK
mIsSecurityProcessed=True -------------------------------- OK
mIsSecurityProcessed=False ------------------------------- OK
mMaxCsmaBackoffs=0 --------------------------------------- OK (12 ms)
mMaxCsmaBackoffs=100 ------------------------------------- OK (541 ms)
mRxChannelAfterTxDone ------------------------------------ OK
mTxDelayBaseTime=now,mTxDelay=500000 --------------------- OK