Skip to content

imxdemetri/ekstra-csi

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ekstra-csi

Full-metadata CSI extraction for MediaTek mt76 chipsets on OpenWrt.

MtkCSIdump gets CSI off the router but strips the metadata -- no source MAC, no RSSI, no SNR, no packet sequence numbers. This library talks directly to the mt76 kernel via netlink and keeps everything the driver provides.

What you get that MtkCSIdump doesn't

Field MtkCSIdump ekstra-csi
Complex I/Q yes yes
Source MAC no yes
RSSI no yes
SNR no yes
Packet sequence no yes
Chain grouping timestamp heuristic exact (pkt_sn + chain_info)
BW80 reassembly manual automatic
Per-device routing no yes
Traffic stimulation no built-in

Why it matters

Without source MAC, every device's CSI blends into one stream. You can't tell if a signal change came from someone walking past the TV or waving at the laptop. With per-device routing, each WiFi link becomes an independent sensor.

With traffic stimulation, beacon-only capture (~2 chains) becomes full data-frame capture (3-6 chains) -- up to a 3x improvement in spatial information per measurement at minimal bandwidth cost.

Requirements

  • OpenWrt router with mt76 CSI patches compiled into the kernel module (tested on OpenWrt One with patches from MtkCSIdump)
  • Python 3.10+
  • Root access on the router (netlink requires it)

Note: Stock OpenWrt firmware does not include the CSI buffering layer. The mt76 driver must be patched to relay CSI events from the firmware MCU to userspace via the nl80211 vendor dump interface. See docs/hardware_validation.md for details.

Quick start

On the router:

pip install ekstra-csi
ekstra-csi-daemon --iface phy0 --port 5500

On your laptop:

from ekstra_csi import CSIClient

for frame in CSIClient("192.168.1.1"):
    print(f"{frame.ta}  rssi={frame.rssi}  snr={frame.snr}  "
          f"{frame.bw_name} {frame.n_chains}ch x {frame.n_sub}sc")

Per-device sensing

from ekstra_csi import CSIClient
from ekstra_csi.demux import DeviceDemux

demux = DeviceDemux(targets=["c8:3a:6b:14:e7:52", "9c:b6:d0:88:f1:03"])
for frame in CSIClient("192.168.1.1"):
    demux.push(frame)

Traffic stimulation

from ekstra_csi import CSIClient, TrafficStimulator

with TrafficStimulator("192.168.1.1"):
    for frame in CSIClient("192.168.1.1"):
        # stimulation increases chains per measurement (more spatial streams)
        H = frame.to_complex()  # (n_chains, n_sub) complex64

Preprocessing

from ekstra_csi.preprocessing import StaticRemover, DFSProfiler

sr = StaticRemover(alpha=0.95)
dfs = DFSProfiler(window=64)

for frame in client:
    H = frame.to_complex()
    dynamic = sr.remove(H[0])  # static environment removed
    dfs.push(dynamic)
    if dfs.ready():
        spectrogram = dfs.extract()  # Doppler velocity spectrogram

Hardware

Tested on the OpenWrt One (MT7981B SoC, mt7976 radio) running OpenWrt 24.10 with CSI patches from MtkCSIdump. Should work on any mt76 device with the CSI vendor extension: GL.iNet, Xiaomi, TP-Link Archer series, and other OpenWrt-supported routers.

BW80 gives 256 real subcarriers across up to 6 chains = 1,536 complex features per measurement. Measured 7-20 Hz depending on signal conditions, with 256 subcarriers confirmed non-zero in every capture. More than Intel 5300 (30 subs x 3 chains = 90) or Nexmon on BCM4366c0 (256 subs x 1 chain = 256). See docs/hardware_validation.md for detailed benchmarks.

Citation

If you use ekstra-csi in academic work, cite:

@inproceedings{ekstra-csi,
  title     = {ekstra-csi: Full-Metadata Channel State Information Extraction
               for MediaTek mt76 WiFi Chipsets},
  author    = {Rodriguez, Demetri},
  year      = {2026},
}

License

Apache-2.0. Academic use requires citation (see LICENSE).

About

CSI extraction toolkit for Ekstra.OS providers on MediaTek mt76 and OpenWrt.

Topics

Resources

License

Stars

5 stars

Watchers

1 watching

Forks

Packages

 
 
 

Contributors

Languages