Code and data for "Same Signal, Different Story: Demystifying Receiver Effects in Wi-Fi Channel State Information". Data and computation results can be found here and the paper here.
A completely over-engineered collection of scripts to run CSI capture experiments with receivers operated by multiple tools. Supports:
- Multiple tools (PicoScenes, Atheros QCA Tool, Nexmon-CSI, ESP32)
- Device-level operation
- Low level shell scripts for common operations (e.g. putting devices into monitor mode)
- WiFi IQ-waveform generation and subsequent USRP transmission
- Frame caching
- Conversion into a shared unified data format (parquet Dataframes)
- Common CSI processing methods
- A helper submodule for long-running experiments (Campaigns)
- Campaign (de-)serialization, allowing to rerun failed parts of campaigns
- Training scripts to replicate our ML experiments on four models under
har-train-scripts
Before being able to use the things in this repo, some things have to be set up. Install what you aim to use: No need to go through Nexmon installation if you only plan on using ESP32s, for example.
Install the underlying tools. This framework only instruments them. Some of them are included as submodule. Links to the relevant pages:
Some installation hints:
-
To start, first initialize all submodules
git submodule update --init --recursive
- Don't forget to run
switch5300Firmware csi - Don't forget to build Picoscenes Python Toolbox
- If you have PicoScenes license, put it in
csi_tools/picoscenes-key.txt - Check
sudo iw devfor wdev interfaces and delete if present (confuses PicoScenes) - For now, you need to manually set the monitor mode using
array_prepare_for_picoscenes. With recent updates, the basic monitor script doesn't do it for the tool anymore. - Install Picoscenes Toolbox
cd csi_tools/PicoscenesToolbox && pip install -e .
Nexmon is a firmware patching framework for broadcom chips. We use it to build a patched firmware that allows to extract CSI from e.g. rt-ac86u asus routers. To set up such a router from scratch first:
- Start by resetting the router (using the hard reset button)
- Possibly update firmware
- Go through the basic config via the web interface (e.g. at
192.168.0.1) - Put the router in AP mode
- Enable ssh access through the web interface
- Give the router a static IP (e.g.
192.168.10.31) - Make sure interface on host has compatible IP (with the subnet) assigned. You can set it manually in the settings or on the command line, e.g.:
sudo ip addr add 192.168.10.1/24 dev eno1We have moved to a new unreleased version of nexmon. We don't build it manually,
but use a precompiled version shared with us that is found inside csi_tools/nexmon-csi.
To set it up:
- Deploy the
routerdirectory onto router(s) - Log onto routers, reload dhd and start ntp
/jffs/CSING/reload.sh && /jffs/CSING/start_ntp.sh
General notes for using the routers:
- Asus routers must be power-cycled whenever you want to reload the kernel module
- Never store captures in
/jffs, since exceeding its36 MBwill brick the device
- Install Matlab together with WLan Toolbox into
matlab_path - Patch Toolbox using
./matlab/patch_wlan_toolbox.sh $matlab_path - Ensure you have a venv active; if not:
python venv .venv && source .venv/bin/activate - Install python engine
cd $matlab_path/extern/engines/python && pip install .
- Install
esp-idfframework - Make sure to use only the
ESP32-S3model for now. We found others to yield corrupted data occasionally. - Install the appropriate
esp-idftoolchain - Build and flash the
espion/csiloggerfirmware - Give yourself rights to access the serial ports:
sudo usermod -a -G tty $USER
sudo usermod -a -G dialout $USER- You might need to
sudo apt remove brltty. Odd, but happens.
Usrpulse is a command server to handle simple frame transmission on the SDR. Install it on the machine which has the device attached and make sure its running (and is available within the network).
Sensei, similarly to sensession, abstracts collection from a few different sources, but efficiently in rust. We use it to capture data from the iwl5300 and nexmon devices via the available python binding.
Installation is simple: Make sure you have rust installed, then:
# make sure you are in your venv, then:
cd ./csi_tools/sensei/py_binding
pip install .-
Populate
sense_config.tomlwith global settings (seeEnvironmentConfigin/src/sensession/config.pyfor options) -
Set up passwordless SSH connections to remote devices in
~/.ssh/config, e.g.Host sdr HostName <ip-address> PreferredAuthentications publickey IdentityFile ~/.ssh/id_rsa User <username>Obviously you will need a key and install that on the remote using
ssh-copy-id.
The tools can be used programmatically. See the examples directory
for a few examples or experiments for the experiment campaigns.
The QCA NIC can not be used to extract CSI when the sounding bit in the phy preamble of the wifi frame is not set. Therefore, this card requires a separately crafted frame to be sent with the SDR compared to the others.
When in monitor mode, the qca will only report CSI from frames with its actual MAC address set in the MAC header.
Probably as part of the firmware, the iwl5300 is not able to extract
CSI in monitor mode from any frames not addressed to the weird magic
MAC address 00:16:ea:12:34:56. We provide a dedicated frame config
InterleavedIQFrameGroupConfig to have alternating frames addressed
to the iwl and the other cards.
Our evaluation scripts can be found under ./evaluation. They are to
be run as modules within the environment where sensession is installed.
For example:
python -m evaluation.tof.compute --help@ARTICLE{samesignal2026,
author={Portner, Fabian and Gringoli, Francesco and Hollick, Matthias and Asadi, Arash},
journal={IEEE Internet of Things Journal},
title={Same Signal, Different Story: Demystifying Receiver Effects in Wi-Fi Channel State Information},
year={2026},
volume={},
number={},
pages={1-1},
keywords={Receivers;Sensors;Nonlinear distortion;Wireless fidelity;Hardware;Gain control;OFDM;Symbols;Accuracy;Wireless sensor networks},
doi={10.1109/JIOT.2026.3667804}}