├── .gitignore ├── LICENSE ├── README.md ├── docs └── bibtex │ └── Touchless-Biometric-User-Authentication-Using-ESP32-WiFi-Module-Citation.bib ├── examples ├── code │ ├── CSI_guard_and_pilot_subcarriers.ipynb │ └── parse_and_plot_example_csi.ipynb ├── esp32_dataset │ └── example_csi.csv └── images │ ├── ESP32_walking_activity.png │ ├── example_CSI_guard_and_pilot_subcarriers.png │ └── example_amplitude_and_phase_graph.png ├── setup.py ├── src ├── __init__.py └── csiparser.py └── tests └── test_csiparser.py /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.toptal.com/developers/gitignore/api/python 3 | # Edit at https://www.toptal.com/developers/gitignore?templates=python 4 | 5 | ### Python ### 6 | # Byte-compiled / optimized / DLL files 7 | __pycache__/ 8 | *.py[cod] 9 | *$py.class 10 | 11 | # C extensions 12 | *.so 13 | 14 | # Distribution / packaging 15 | .Python 16 | build/ 17 | develop-eggs/ 18 | dist/ 19 | downloads/ 20 | eggs/ 21 | .eggs/ 22 | parts/ 23 | sdist/ 24 | var/ 25 | wheels/ 26 | pip-wheel-metadata/ 27 | share/python-wheels/ 28 | *.egg-info/ 29 | .installed.cfg 30 | *.egg 31 | MANIFEST 32 | 33 | # PyInstaller 34 | # Usually these files are written by a python script from a template 35 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 36 | *.manifest 37 | *.spec 38 | 39 | # Installer logs 40 | pip-log.txt 41 | pip-delete-this-directory.txt 42 | 43 | # Unit test / coverage reports 44 | htmlcov/ 45 | .tox/ 46 | .nox/ 47 | .coverage 48 | .coverage.* 49 | .cache 50 | nosetests.xml 51 | coverage.xml 52 | *.cover 53 | *.py,cover 54 | .hypothesis/ 55 | .pytest_cache/ 56 | pytestdebug.log 57 | 58 | # Translations 59 | *.mo 60 | *.pot 61 | 62 | # Django stuff: 63 | *.log 64 | local_settings.py 65 | db.sqlite3 66 | db.sqlite3-journal 67 | 68 | # Flask stuff: 69 | instance/ 70 | .webassets-cache 71 | 72 | # Scrapy stuff: 73 | .scrapy 74 | 75 | # Sphinx documentation 76 | docs/_build/ 77 | doc/_build/ 78 | 79 | # PyBuilder 80 | target/ 81 | 82 | # Jupyter Notebook 83 | .ipynb_checkpoints 84 | 85 | # IPython 86 | profile_default/ 87 | ipython_config.py 88 | 89 | # pyenv 90 | .python-version 91 | 92 | # pipenv 93 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 94 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 95 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 96 | # install all needed dependencies. 97 | #Pipfile.lock 98 | 99 | # poetry 100 | #poetry.lock 101 | 102 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 103 | __pypackages__/ 104 | 105 | # Celery stuff 106 | celerybeat-schedule 107 | celerybeat.pid 108 | 109 | # SageMath parsed files 110 | *.sage.py 111 | 112 | # Environments 113 | # .env 114 | .env/ 115 | .venv/ 116 | env/ 117 | venv/ 118 | ENV/ 119 | env.bak/ 120 | venv.bak/ 121 | pythonenv* 122 | 123 | # Spyder project settings 124 | .spyderproject 125 | .spyproject 126 | 127 | # Rope project settings 128 | .ropeproject 129 | 130 | # mkdocs documentation 131 | /site 132 | 133 | # mypy 134 | .mypy_cache/ 135 | .dmypy.json 136 | dmypy.json 137 | 138 | # Pyre type checker 139 | .pyre/ 140 | 141 | # pytype static type analyzer 142 | .pytype/ 143 | 144 | # operating system-related files 145 | # file properties cache/storage on macOS 146 | *.DS_Store 147 | # thumbnail cache on Windows 148 | Thumbs.db 149 | 150 | # profiling data 151 | .prof 152 | 153 | # Ignore vscode file 154 | .vscode 155 | 156 | # End of https://www.toptal.com/developers/gitignore/api/python -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Rikesh Makwana 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ESP32 Wi-Fi CSI Python Parser 2 | 3 | 4 | 5 | This is a Python parser for ESP32 Wi-Fi Channel State Information (CSI) based on the ESP32 CSI specification. 6 | 7 | This project uses [ESP CSI Toolkit](https://stevenmhernandez.github.io/ESP32-CSI-Tool/) created by Hernandez and Bulut. 8 | 9 | ## Installation 10 | 11 | Run the following to install: 12 | 13 | ```python 14 | pip install csiparser 15 | ``` 16 | 17 | ## Usage 18 | 19 | ```python 20 | # Import ESP32 CSI parser 21 | import csiparser 22 | 23 | # Parse and filter CSI data 24 | example_csi = ( 25 | csiparser.ESP32("../esp32_dataset/example_csi.csv") 26 | .filter_by_sig_mode(1) 27 | .get_csi() 28 | .remove_null_subcarriers() 29 | .get_amplitude_from_csi() 30 | .get_phase_from_csi() 31 | ) 32 | 33 | # Retrieve example amplitude 34 | example_amplitude = example_csi.amplitude 35 | 36 | # Retrieve example phase 37 | example_phase = example_csi.phase 38 | ``` 39 | Further the amplitude and phase information can be plotted to visualize the distortion in amplitude and phase shift as follows: 40 | 41 | ![Example Amplitude and Phase Graph](https://raw.githubusercontent.com/RikeshMMM/ESP32-CSI-Python-Parser/main/examples/images/example_amplitude_and_phase_graph.png) 42 | 43 | _See [Examples](./examples) directory for full example._ 44 | 45 | ## Citation 46 | 47 | [Cite this Tool with BibTeX](https://raw.githubusercontent.com/RikeshMMM/ESP32-CSI-Python-Parser/main/docs/bibtex/Touchless-Biometric-User-Authentication-Using-ESP32-WiFi-Module-Citation.bib) 48 | 49 | ## License 50 | Distributed under the MIT License. See LICENSE for more information. 51 | -------------------------------------------------------------------------------- /docs/bibtex/Touchless-Biometric-User-Authentication-Using-ESP32-WiFi-Module-Citation.bib: -------------------------------------------------------------------------------- 1 | @InProceedings{10.1007/978-981-16-7618-5_46, 2 | author="Makwana, Rikesh 3 | and Shaikh, Talal", 4 | editor="Ullah, Abrar 5 | and Anwar, Sajid 6 | and Rocha, {\'A}lvaro 7 | and Gill, Steve", 8 | title="Touchless Biometric User Authentication Using ESP32 WiFi Module", 9 | booktitle="Proceedings of International Conference on Information Technology and Applications", 10 | year="2022", 11 | publisher="Springer Nature Singapore", 12 | address="Singapore", 13 | pages="527--537", 14 | abstract="Due to the ubiquitous nature of WiFi, the use of WiFi signals for Biometric User Authentication (BUA) is ongoing research which has previously focused on using multi-antenna commercial off-the-shelf (COTS) devices such as Intel 5300 or Atheros 9390. However, due to high cost and limited availability, COTS devices are restricted to small scale deployment. To overcome this issue, researchers propose using Espressif ESP32, an inexpensive single antenna microcontroller equipped with WiFi and Bluetooth modules capable of capturing detailed WiFi Channel State Information (CSI). This paper explores and extends the application of ESP32 by proposing a model for device-less and touch-less BUA systems using a simple client--server architecture. The system identifies users as they perform day-to-day activities by recognizing behavioural and physiological characteristics using LSTM---a deep learning approach. Furthermore, the paper describes the Python tool developed for parsing and filtering WiFi CSI data.", 15 | isbn="978-981-16-7618-5" 16 | } 17 | 18 | -------------------------------------------------------------------------------- /examples/images/ESP32_walking_activity.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RikeshMMM/ESP32-CSI-Python-Parser/734985e1a6ed385b0fa7919ec4606db281094e7e/examples/images/ESP32_walking_activity.png -------------------------------------------------------------------------------- /examples/images/example_CSI_guard_and_pilot_subcarriers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RikeshMMM/ESP32-CSI-Python-Parser/734985e1a6ed385b0fa7919ec4606db281094e7e/examples/images/example_CSI_guard_and_pilot_subcarriers.png -------------------------------------------------------------------------------- /examples/images/example_amplitude_and_phase_graph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RikeshMMM/ESP32-CSI-Python-Parser/734985e1a6ed385b0fa7919ec4606db281094e7e/examples/images/example_amplitude_and_phase_graph.png -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | with open("README.md", "r") as fh: 4 | long_description = fh.read() 5 | 6 | classifiers = [ 7 | 'Development Status :: 1 - Planning', 8 | 'Intended Audience :: Education', 9 | 'Operating System :: OS Independent', 10 | 'License :: OSI Approved :: MIT License', 11 | 'Programming Language :: Python :: 3', 12 | 'Programming Language :: Python :: 3.8', 13 | ] 14 | 15 | setup( 16 | name='csiparser', 17 | version='0.1.0', 18 | description='A Python Package to Parse ESP32 Wi-Fi CSI Data', 19 | py_modules=["csiparser"], 20 | package_dir={'': 'src'}, 21 | long_description=long_description, 22 | long_description_content_type="text/markdown", 23 | install_requires = [ 24 | "numpy ~= 1.19.5", 25 | "pandas ~= 1.2.3", 26 | ], 27 | extras_require = { 28 | "dev": [ 29 | "pytest>=3.7" 30 | ], 31 | }, 32 | url="https://github.com/RikeshMMM/ESP32-CSI-Python-Parser", 33 | author="Rikesh Makwana", 34 | author_email="rikesh.makwana@gmail.com", 35 | ) -------------------------------------------------------------------------------- /src/__init__.py : -------------------------------------------------------------------------------- 1 | from .csiparser import ESP32 -------------------------------------------------------------------------------- /src/csiparser.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import pandas as pd 3 | import re 4 | 5 | class ESP32: 6 | """Parse ESP32 Wi-Fi Channel State Information (CSI) obtained using ESP32 CSI Toolkit by Hernandez and Bulut. 7 | ESP32 CSI Toolkit: https://stevenmhernandez.github.io/ESP32-CSI-Tool/ 8 | """ 9 | 10 | # View README.md for more information on null subcarriers 11 | NULL_SUBCARRIERS = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 64, 65, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 382, 383] 12 | 13 | def __init__(self, csi_file): 14 | self.csi_file = csi_file 15 | self.__read_file() 16 | 17 | def __read_file(self): 18 | """Read RAW CSI file (.csv) using Pandas and return a Pandas dataframe 19 | """ 20 | self.csi_df = pd.read_csv(self.csi_file) 21 | 22 | def seek_file(self): 23 | """Seek RAW CSI file 24 | """ 25 | return self.csi_df 26 | 27 | def filter_by_sig_mode(self, sig_mode): 28 | """Filter CSI data by signal mode 29 | Args: 30 | sig_mode (int): 31 | 0 : Non - High Throughput Signals (non-HT) 32 | 1 : HIgh Throughput Signals (HT) 33 | """ 34 | self.csi_df = self.csi_df.loc[self.csi_df['sig_mode'] == sig_mode] 35 | return self 36 | 37 | def get_csi(self): 38 | """Read CSI string as Numpy array 39 | 40 | The CSI data collected by ESP32 contains channel frequency responses (CFR) represented by two signed bytes (imaginary, real) for each sub-carriers index 41 | The length (bytes) of the CSI sequency depends on the CFR type 42 | CFR consist of legacy long training field (LLTF), high-throughput LTF (HT-LTF), and space- time block code HT-LTF (STBC-HT-LTF) 43 | Ref: https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/wifi.html#wi-fi-channel-state-information 44 | 45 | NOTE: Not all 3 field may not be present (as represented in table and configuration) 46 | """ 47 | raw_csi_data = self.csi_df['CSI_DATA'].copy() 48 | csi_data = np.array([np.fromstring(csi_datum.strip('[ ]'), dtype=int, sep = ' ') for csi_datum in raw_csi_data]) 49 | self.csi_data = csi_data 50 | return self 51 | 52 | # NOTE: Currently does not provide support for all signal subcarrier types 53 | def remove_null_subcarriers(self): 54 | """Remove NULL subcarriers from CSI 55 | """ 56 | 57 | # Non-HT Signals (20 Mhz) - non STBC 58 | if self.csi_data.shape[1] == 128: 59 | remove_null_subcarriers = self.NULL_SUBCARRIERS[:24] 60 | # HT Signals (40 Mhz) - non STBC 61 | elif self.csi_data.shape[1] == 384: 62 | remove_null_subcarriers = self.NULL_SUBCARRIERS 63 | else: 64 | return self 65 | 66 | csi_data_T = self.csi_data.T 67 | csi_data_T_clean = np.delete(csi_data_T, remove_null_subcarriers, 0) 68 | csi_data_clean = csi_data_T_clean.T 69 | self.csi_data = csi_data_clean 70 | 71 | return self 72 | 73 | def get_amplitude_from_csi(self): 74 | """Calculate the Amplitude (or Magnitude) from CSI 75 | Ref: https://farside.ph.utexas.edu/teaching/315/Waveshtml/node88.html 76 | """ 77 | amplitude = np.array([np.sqrt(data[::2]**2 + data[1::2]**2) for data in self.csi_data]) 78 | self.amplitude = amplitude 79 | return self 80 | 81 | def get_phase_from_csi(self): 82 | """Calculate the Amplitude (or Magnitude) from CSI 83 | Ref: https://farside.ph.utexas.edu/teaching/315/Waveshtml/node88.html 84 | """ 85 | phase = np.array([np.arctan2(data[::2], data[1::2]) for data in self.csi_data]) 86 | self.phase = phase 87 | return self 88 | 89 | -------------------------------------------------------------------------------- /tests/test_csiparser.py: -------------------------------------------------------------------------------- 1 | from csiparser import ESP32 2 | 3 | csifilepath = "examples/esp32_dataset/example_csi.csv" 4 | csifile = ESP32(csifilepath) 5 | 6 | def test_read_file(): 7 | assert csifile.read_file().shape[0] > 0 and csifile.read_file().shape[1] == 27 --------------------------------------------------------------------------------