Skip to content

wshanmu/UWB_lab

Repository files navigation

UWB Range-Only Lab Guide

This lab uses two Qorvo DWM3001CDK UWB boards to run FiRa two-way ranging between two wrists or forearms. You will use the inter-board distance signal for ranging analysis and bimanual activity recognition.

The folder distributed for this lab is named UWB_lab and already contains the uwb-qorvo-tools subfolder. The scripts in this folder use relative paths, so the folder can be placed anywhere on your laptop.

Each board should already have the UCI firmware. Do not reflash the boards unless the instructor explicitly asks you to.

1. Set Up The Qorvo Tool

Use Terminal on macOS. Use PowerShell or Anaconda PowerShell Prompt on Windows. The Windows examples below use PowerShell syntax.

The full Qorvo install command, python -m pip install ., is not required for this lab. We will run the Python scripts directly from the distributed folder. The important part is to tell Python where the Qorvo libraries are.

macOS:

cd UWB_lab/uwb-qorvo-tools
conda activate cosmos-ds
export UWB_TOOLS=$(pwd)
export PYTHONPATH="$UWB_TOOLS/lib/uwb-uci:$UWB_TOOLS/lib/uqt-utils:$UWB_TOOLS:$PYTHONPATH"

Windows PowerShell:

cd UWB_lab\uwb-qorvo-tools
conda activate cosmos-ds
$env:UWB_TOOLS=(Get-Location).Path
$env:PYTHONPATH="$env:UWB_TOOLS\lib\uwb-uci;$env:UWB_TOOLS\lib\uqt-utils;$env:UWB_TOOLS;$env:PYTHONPATH"

If Python later reports No module named serial or No module named colorama, install the small runtime dependencies:

python -m pip install pyserial colorama toml

2. Identify The Serial Ports

Connect both DWM3001CDK boards over USB.

macOS:

ls /dev/cu.usbmodem*

Windows:

Open Device Manager and look under Ports (COM & LPT). You should see two COM ports, such as COM7 and COM8.

You need one controller/initiator board and one controlee/responder board. Record both port names.

Form your group in the class sheet, then use the sheet-assigned RF settings:

https://docs.google.com/spreadsheets/d/1xEM-HSpBDMrXdBSz2BphMjZDY_hA6qhXVAFCRKVvYuw/edit?usp=sharing

There are eight groups total. Each group will use one preamble code from 9, 10, 11, 12 and one UWB channel from 5, 9. Use the values in the sheet, not your group number, as the FiRa preamble code and channel.

macOS setup example:

export CONTROLLER_PORT=/dev/cu.usbmodemD46FFE3655DD1
export CONTROLEE_PORT=/dev/cu.usbmodemE89E195B6C731
export GROUP_ID=1
export PREAMBLE_CODE=9
export UWB_CHANNEL=5

Windows PowerShell setup example:

$env:CONTROLLER_PORT="COM7"
$env:CONTROLEE_PORT="COM8"
$env:GROUP_ID="1"
$env:PREAMBLE_CODE="9"
$env:UWB_CHANNEL="5"

export on macOS and $env: on Windows create terminal variables that later commands can read. They only work inside the current terminal. If you open a new terminal or tab, run these setup commands again.

3. Check Both Boards

Run this once for each board.

macOS:

python scripts/device/get_device_info/get_device_info.py -p $CONTROLLER_PORT
python scripts/device/get_device_info/get_device_info.py -p $CONTROLEE_PORT

Windows PowerShell:

python scripts\device\get_device_info\get_device_info.py -p $env:CONTROLLER_PORT
python scripts\device\get_device_info\get_device_info.py -p $env:CONTROLEE_PORT

Both commands should report status: Ok.

If a board does not respond, check that no other terminal is using the same port, unplug and reconnect the board, then run the command again.

4. Use Your Sheet-Assigned Preamble Code And Channel

Each group must use the preamble code and channel assigned in the class sheet:

--channel <your assigned channel>
--preamble-idx <your assigned preamble code>

For example, if the sheet assigns group 1 to preamble code 9 and channel 5:

--channel 5
--preamble-idx 9

Both boards in the same group must use the same preamble code and channel. The eight groups are split across preamble codes 9, 10, 11, 12 and channels 5, 9 to reduce interference when multiple groups range at the same time.

5. Run The Default FiRa TWR Demo

Open two terminals. In both terminals, first enter the tools folder, activate the environment, and set the variables.

macOS:

cd UWB_lab/uwb-qorvo-tools
conda activate cosmos-ds
export UWB_TOOLS=$(pwd)
export PYTHONPATH="$UWB_TOOLS/lib/uwb-uci:$UWB_TOOLS/lib/uqt-utils:$UWB_TOOLS:$PYTHONPATH"
export CONTROLLER_PORT=/dev/cu.usbmodemD46FFE3655DD1
export CONTROLEE_PORT=/dev/cu.usbmodemE89E195B6C731
export GROUP_ID=1
export PREAMBLE_CODE=9
export UWB_CHANNEL=5

Windows PowerShell:

cd UWB_lab\uwb-qorvo-tools
conda activate cosmos-ds
$env:UWB_TOOLS=(Get-Location).Path
$env:PYTHONPATH="$env:UWB_TOOLS\lib\uwb-uci;$env:UWB_TOOLS\lib\uqt-utils;$env:UWB_TOOLS;$env:PYTHONPATH"
$env:CONTROLLER_PORT="COM7"
$env:CONTROLEE_PORT="COM8"
$env:GROUP_ID="1"
$env:PREAMBLE_CODE="9"
$env:UWB_CHANNEL="5"

Replace the two port values, group ID, preamble code, and channel with your own values from the class sheet.

Terminal 1, start the controlee first.

macOS:

python scripts/fira/run_fira_twr/run_fira_twr.py \
  -p $CONTROLEE_PORT \
  --controlee \
  --channel $UWB_CHANNEL \
  --preamble-idx $PREAMBLE_CODE \
  --aoa-report all-disabled \
  -t 35

Windows PowerShell:

python scripts\fira\run_fira_twr\run_fira_twr.py `
  -p $env:CONTROLEE_PORT `
  --controlee `
  --channel $env:UWB_CHANNEL `
  --preamble-idx $env:PREAMBLE_CODE `
  --aoa-report all-disabled `
  -t 35

Terminal 2, start the controller second.

macOS:

python scripts/fira/run_fira_twr/run_fira_twr.py \
  -p $CONTROLLER_PORT \
  --channel $UWB_CHANNEL \
  --preamble-idx $PREAMBLE_CODE \
  --aoa-report all-disabled \
  -t 30

Windows PowerShell:

python scripts\fira\run_fira_twr\run_fira_twr.py `
  -p $env:CONTROLLER_PORT `
  --channel $env:UWB_CHANNEL `
  --preamble-idx $env:PREAMBLE_CODE `
  --aoa-report all-disabled `
  -t 30

The DWM3001CDK setup used in this lab does not use AoA, so AoA reporting is disabled.

Successful output contains lines like:

status: Ok (0x0)
distance: <value> cm

6. Configure The Update Rate

The update rate is controlled mainly by --ranging-span, which is the ranging interval in milliseconds:

FPS = 1000 / ranging-span

The default script uses:

--slot-span 2400
--slots-per-rr 25
--ranging-span 200

This gives about:

1000 / 200 = 5 FPS

For one controller and one controlee, this lab should run near 50 FPS. You will configure the timing values yourself.

slot duration (ms) = slot-span / 1200
minimum ranging-span = slot duration * slots-per-rr
target ranging-span = 1000 / target FPS

For this lab, use:

target FPS = 50
slot-span = 2400
slots-per-rr = 6

TODO: compute a valid RANGING_SPAN value. It must be large enough for all slots in the ranging round, and it should give about 50 FPS.

Set these variables in both terminals after you fill in the TODO.

macOS:

export SLOT_SPAN=2400
export SLOTS_PER_RR=6
export RANGING_SPAN=TODO_RANGING_SPAN

Windows PowerShell:

$env:SLOT_SPAN="2400"
$env:SLOTS_PER_RR="6"
$env:RANGING_SPAN="TODO_RANGING_SPAN"

Again, these variables only exist in the current terminal. If you open a new terminal, run them again.

7. Run 50 Hz Ranging And Save JSON

After setting the timing variables, run the experiment again and save the generated JSON files.

macOS, create output folders:

mkdir -p "$UWB_TOOLS/lab_logs/group_${GROUP_ID}/controller"
mkdir -p "$UWB_TOOLS/lab_logs/group_${GROUP_ID}/controlee"

Windows PowerShell, create output folders:

New-Item -ItemType Directory -Force -Path "$env:UWB_TOOLS\lab_logs\group_$($env:GROUP_ID)\controller" | Out-Null
New-Item -ItemType Directory -Force -Path "$env:UWB_TOOLS\lab_logs\group_$($env:GROUP_ID)\controlee" | Out-Null

Terminal 1, controlee.

macOS:

cd "$UWB_TOOLS/lab_logs/group_${GROUP_ID}/controlee"
python "$UWB_TOOLS/scripts/fira/run_fira_twr/run_fira_twr.py" \
  -p $CONTROLEE_PORT \
  --controlee \
  --channel $UWB_CHANNEL \
  --preamble-idx $PREAMBLE_CODE \
  --aoa-report all-disabled \
  --slot-span $SLOT_SPAN \
  --slots-per-rr $SLOTS_PER_RR \
  --ranging-span $RANGING_SPAN \
  --diag_dump \
  -t 35

Windows PowerShell:

cd "$env:UWB_TOOLS\lab_logs\group_$($env:GROUP_ID)\controlee"
python "$env:UWB_TOOLS\scripts\fira\run_fira_twr\run_fira_twr.py" `
  -p $env:CONTROLEE_PORT `
  --controlee `
  --channel $env:UWB_CHANNEL `
  --preamble-idx $env:PREAMBLE_CODE `
  --aoa-report all-disabled `
  --slot-span $env:SLOT_SPAN `
  --slots-per-rr $env:SLOTS_PER_RR `
  --ranging-span $env:RANGING_SPAN `
  --diag_dump `
  -t 35

Terminal 2, controller.

macOS:

cd "$UWB_TOOLS/lab_logs/group_${GROUP_ID}/controller"
python "$UWB_TOOLS/scripts/fira/run_fira_twr/run_fira_twr.py" \
  -p $CONTROLLER_PORT \
  --channel $UWB_CHANNEL \
  --preamble-idx $PREAMBLE_CODE \
  --aoa-report all-disabled \
  --slot-span $SLOT_SPAN \
  --slots-per-rr $SLOTS_PER_RR \
  --ranging-span $RANGING_SPAN \
  --diag_dump \
  -t 30

Windows PowerShell:

cd "$env:UWB_TOOLS\lab_logs\group_$($env:GROUP_ID)\controller"
python "$env:UWB_TOOLS\scripts\fira\run_fira_twr\run_fira_twr.py" `
  -p $env:CONTROLLER_PORT `
  --channel $env:UWB_CHANNEL `
  --preamble-idx $env:PREAMBLE_CODE `
  --aoa-report all-disabled `
  --slot-span $env:SLOT_SPAN `
  --slots-per-rr $env:SLOTS_PER_RR `
  --ranging-span $env:RANGING_SPAN `
  --diag_dump `
  -t 30

--diag_dump writes range_data_*.json files into the folder where the command is run. Save the controller JSON file for your ranging analysis. The controller terminal should show a ranging interval close to the value you computed for RANGING_SPAN.

8. Use The Ranging Experiment Wrapper

The manual two-terminal workflow is useful for learning the tool. For repeated experiments, use ranging_experiment_wrapper.py from the main UWB_lab folder.

The wrapper:

  • resets both boards before the run unless --skip-device-reset is used
  • starts the controlee first and controller second
  • runs both Qorvo commands with the requested timing
  • saves terminal logs for both boards
  • parses distance measurements into ranging_samples.csv
  • writes session metadata and a ranging summary
  • optionally displays a live distance plot

Run a 30-second 50 Hz session with a live plot.

macOS:

cd UWB_lab
conda activate cosmos-ds

python ranging_experiment_wrapper.py \
  --controller-port $CONTROLLER_PORT \
  --controlee-port $CONTROLEE_PORT \
  --group-id $GROUP_ID \
  --preamble-code $PREAMBLE_CODE \
  --channel $UWB_CHANNEL \
  --duration 30 \
  --fps 50 \
  --session-name group_${GROUP_ID}_range_demo \
  --visualize

Windows PowerShell:

cd UWB_lab
conda activate cosmos-ds

python ranging_experiment_wrapper.py `
  --controller-port $env:CONTROLLER_PORT `
  --controlee-port $env:CONTROLEE_PORT `
  --group-id $env:GROUP_ID `
  --preamble-code $env:PREAMBLE_CODE `
  --channel $env:UWB_CHANNEL `
  --duration 30 `
  --fps 50 `
  --session-name "group_$($env:GROUP_ID)_range_demo" `
  --visualize

The output folder will be under:

UWB_lab/sessions/group_<GROUP_ID>_range_demo/

To plot the ranging distribution, first finish the KDE TODO in analyze_ranging_results.py, then run:

macOS:

python analyze_ranging_results.py \
  sessions/group_${GROUP_ID}_range_demo \
  --side controller \
  --show

Windows PowerShell:

python analyze_ranging_results.py `
  "sessions\group_$($env:GROUP_ID)_range_demo" `
  --side controller `
  --show

9. Ranging Accuracy Experiment

Collect two controlled ranging trials:

  1. Place the boards about 50 cm apart with clear line of sight.
  2. Run the wrapper for 30 seconds and save the session.
  3. Place the boards as far apart as possible up to about 2.5 m.
  4. Run the wrapper for another 30 seconds and save the session.
  5. Finish the TODO in analyze_ranging_results.py, then plot the KDE distribution for each session.

In your notes, report:

  • target distance
  • mean and median measured distance
  • spread or stability of the measurements
  • visible outliers or dropped measurements
  • whether the long-distance trial is less stable than the 50 cm trial

10. Bimanual Activity Recognition

Final question:

Can we recognize two-handed activities using only the distance between a
person's two wrists?

Place one DWM3001CDK board on each wrist or forearm. The classifier will use only the inter-hand distance signal.

Possible activity classes:

  • clapping
  • opening and closing arms repeatedly
  • passing an object between hands
  • folding a towel
  • stirring or mixing while holding a container
  • putting on a glove
  • tying or untying a simple knot
  • resting or unrelated movement

Use short, repeatable gestures. For a first dataset, choose 3 or 4 classes and collect multiple trials for each class.

11. Collect A Gesture Dataset

From UWB_lab, run the collector.

macOS:

python collect_dataset.py \
  --controller-port $CONTROLLER_PORT \
  --controlee-port $CONTROLEE_PORT \
  --group-id $GROUP_ID \
  --preamble-code $PREAMBLE_CODE \
  --channel $UWB_CHANNEL \
  --collector student01 \
  --gesture clapping,t-arm,boxing,resting \
  --trials 8 \
  --trial-duration 3 \
  --pause 3 \
  --fps 50

Windows PowerShell:

python collect_dataset.py `
  --controller-port $env:CONTROLLER_PORT `
  --controlee-port $env:CONTROLEE_PORT `
  --group-id $env:GROUP_ID `
  --preamble-code $env:PREAMBLE_CODE `
  --channel $env:UWB_CHANNEL `
  --collector student01 `
  --gesture clapping,open_close_arms,passing_object,resting `
  --trials 8 `
  --trial-duration 5 `
  --pause 3 `
  --fps 50

The collector starts one continuous ranging session, gives a countdown for each trial, saves accepted trial windows, and asks whether to keep or redo each trial.

Dataset structure:

datasets/gesture_dataset_YYYYMMDD_HHMMSS/
  dataset_metadata.json
  trials.csv
  device_reset_log.txt
  final_device_reset_log.txt
  continuous_session/
  sessions/

sessions/range_student01_clapping_trial_001/
  trial_metadata.json
  session_metadata.json
  controller/ranging_samples.csv
  controlee/

trials.csv is the manifest used by the training script.

12. Combine Datasets From Multiple Students

After each student collects data, combine the dataset folders.

macOS:

python combine_datasets.py \
  datasets/gesture_dataset_YYYYMMDD_HHMMSS \
  datasets/gesture_dataset_YYYYMMDD_HHMMSS \
  --output datasets/combined_range_all_students

Windows PowerShell:

python combine_datasets.py `
  datasets\gesture_dataset_YYYYMMDD_HHMMSS `
  datasets\gesture_dataset_YYYYMMDD_HHMMSS `
  --output datasets\combined_range_all_students

You can filter by collector or gesture:

macOS:

python combine_datasets.py \
  datasets/gesture_dataset_YYYYMMDD_HHMMSS \
  datasets/gesture_dataset_YYYYMMDD_HHMMSS \
  --collector student01 \
  --gesture clapping,resting \
  --output datasets/combined_subset

Windows PowerShell:

python combine_datasets.py `
  datasets\gesture_dataset_YYYYMMDD_HHMMSS `
  datasets\gesture_dataset_YYYYMMDD_HHMMSS `
  --collector student01 `
  --gesture clapping,resting `
  --output datasets\combined_subset

13. Train A Range-Based Classifier

Train a baseline KNN model.

macOS:

python train.py datasets/gesture_dataset_YYYYMMDD_HHMMSS \
  --side controller \
  --classifier knn \
  --knn-neighbors 5

Windows PowerShell:

python train.py datasets\gesture_dataset_YYYYMMDD_HHMMSS `
  --side controller `
  --classifier knn `
  --knn-neighbors 5

The starter code gives you two classifiers:

# KNN
python train.py datasets/combined_range_all_students \
  --side controller \
  --classifier knn \
  --knn-neighbors 5 \
  --knn-weights distance

# Linear SVM
python train.py datasets/combined_range_all_students \
  --side controller \
  --classifier svm_linear \
  --svm-c 1.0

For Windows PowerShell, use backticks instead of backslashes for multi-line commands.

TODO: try at least one additional classifier by editing build_classifier() in train.py. Useful references:

Choose the proposed gesture-specific feature set with --feature-set proposal. The starter proposal extractor currently returns the original baseline features and 10 placeholder values. Your task is to replace the placeholders in extract_range_features_proposal() with lightweight features that help separate the activities. Train the same classifier with both feature sets to compare the difference:

# Original compact baseline features
python train.py datasets/combined_range_all_students \
  --side controller \
  --feature-set baseline \
  --classifier knn

# Baseline + your proposal features
python train.py datasets/combined_range_all_students \
  --side controller \
  --feature-set proposal \
  --close-threshold-cm 25 \
  --classifier knn

Train on a combined dataset:

macOS:

python train.py datasets/combined_range_all_students --side controller

Windows PowerShell:

python train.py datasets\combined_range_all_students --side controller

The range feature vector includes summary statistics and a resampled distance shape over each trial window:

  • count, mean, standard deviation, min, max, median
  • quartiles, interquartile range, range
  • first value, last value, total change
  • average and maximum step change
  • linear trend
  • resampled distance trajectory

These are the raw baseline features. For your proposal features, look for signals that are specific to two-handed motion, such as closeness, repeated hand contacts, opening/closing trends, movement amount, direction changes, and the spacing between valleys in the distance signal. Keep the features cheap enough to compute during real-time evaluation.

To make the trajectory portion denser:

macOS:

python train.py datasets/combined_range_all_students \
  --side controller \
  --resample-points 40

Windows PowerShell:

python train.py datasets\combined_range_all_students `
  --side controller `
  --resample-points 40

Training outputs:

models/CLASSIFIER_range_YYYYMMDD_HHMMSS.joblib
models/CLASSIFIER_range_YYYYMMDD_HHMMSS_confusion_matrix.png
models/CLASSIFIER_range_YYYYMMDD_HHMMSS_summary.json

CLASSIFIER will be knn, svm_linear, or a method you add.

14. Evaluate Generalization Across Students

Random train/test splits can overestimate performance if the same student appears in both sets. For a stronger test, hold out one collector:

macOS:

python train.py datasets/combined_range_all_students \
  --side controller \
  --test-collector student03

Windows PowerShell:

python train.py datasets\combined_range_all_students `
  --side controller `
  --test-collector student03

Compare the random split accuracy and the leave-one-collector-out accuracy.

15. Run Real-Time Gesture Evaluation

Use the trained model with a live sliding window.

The real-time evaluator does not need a classifier flag. The .joblib file stores the trained classifier, so the same command works for KNN, linear SVM, and any other classifier you add. The evaluator calls the saved model's predict method. If the model supports predict_proba, it also shows a confidence value. The starter linear SVM model enables probability output. The .joblib file also stores the feature set, so models trained with --feature-set proposal automatically use the proposal feature extractor during real-time evaluation.

By default, eval_realtime.py uses the timing stored in the trained model when that metadata is available. Keep --fps, --ranging-span, --slot-span, --slots-per-rr, and --window-seconds consistent with the data collection settings unless you are intentionally testing a different setup. Add --strict-model-config if you want the script to stop instead of only warning when the evaluation timing differs from the model metadata.

By default, the evaluator shows raw predictions with --vote-window 1. After you implement majority_vote() in eval_realtime.py, try --vote-window 5 to smooth the displayed result over recent prediction windows.

macOS:

python eval_realtime.py \
  --model datasets/combined_range_all_students/models/CLASSIFIER_range_YYYYMMDD_HHMMSS.joblib \
  --controller-port $CONTROLLER_PORT \
  --controlee-port $CONTROLEE_PORT \
  --group-id $GROUP_ID \
  --preamble-code $PREAMBLE_CODE \
  --channel $UWB_CHANNEL \
  --duration 60 \
  --step-seconds 0.5 \
  --vote-window 1 \
  --visualize

Windows PowerShell:

python eval_realtime.py `
  --model datasets\combined_range_all_students\models\CLASSIFIER_range_YYYYMMDD_HHMMSS.joblib `
  --controller-port $env:CONTROLLER_PORT `
  --controlee-port $env:CONTROLEE_PORT `
  --group-id $env:GROUP_ID `
  --preamble-code $env:PREAMBLE_CODE `
  --channel $env:UWB_CHANNEL `
  --duration 60 `
  --step-seconds 0.5 `
  --vote-window 1 `
  --visualize

The evaluator starts a live ranging session, keeps a recent distance window, extracts the same range features used during training, and writes predictions to:

sessions/eval_group_<GROUP_ID>_<timestamp>/realtime_predictions.csv

TODO: implement majority voting in eval_realtime.py. The function should take recent raw predictions, return the most common class, report a vote fraction, and handle ties in a reasonable way.

16. Suggested Student TODOs

Use the working pipeline above, then improve it:

  • compute and verify a valid 50 Hz ranging configuration
  • implement the KDE plot in analyze_ranging_results.py
  • design proposal features in extract_range_features_proposal() inside uwb_lab_common.py
  • add and compare at least one classifier beyond KNN and linear SVM
  • implement majority voting in eval_realtime.py
  • compare different trial lengths and sliding-window lengths
  • evaluate random split versus leave-one-collector-out split
  • inspect the confusion matrix and identify which activities are confused
  • add a better visualization for distance over time during each collected trial
  • decide which activities are realistic to classify using distance only

Troubleshooting

If ranging does not start:

  • confirm both boards are plugged in
  • confirm each terminal has the correct terminal variables
  • confirm no other terminal is using the same serial port
  • restart from the controlee first, then the controller
  • unplug and reconnect both boards if the serial ports stop responding

If the update rate is wrong:

  • check that SLOT_SPAN, SLOTS_PER_RR, and RANGING_SPAN were set in both terminals
  • confirm the controller prints a ranging interval close to your computed RANGING_SPAN
  • remember that terminal variables do not carry over to a new terminal

If plots fail to open:

  • make sure conda activate cosmos-ds is active
  • install the plotting dependencies in that environment
  • rerun the command after the environment is fixed

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages