Add basic preprocessing and visualisation

This commit is contained in:
Christos Falas 2024-11-25 12:15:35 +00:00
commit 841da23c47
No known key found for this signature in database
9 changed files with 751 additions and 0 deletions

176
.gitignore vendored Normal file
View File

@ -0,0 +1,176 @@
# Created by https://www.toptal.com/developers/gitignore/api/python
# Edit at https://www.toptal.com/developers/gitignore?templates=python
### Python ###
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock
# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
# in version control.
# https://pdm.fming.dev/#use-with-ide
.pdm.toml
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
### Python Patch ###
# Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration
poetry.toml
# ruff
.ruff_cache/
# LSP config files
pyrightconfig.json
# End of https://www.toptal.com/developers/gitignore/api/python

14
Pipfile Normal file
View File

@ -0,0 +1,14 @@
[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"
[packages]
flask = "*"
numpy = "*"
flask-sock = "*"
[dev-packages]
[requires]
python_version = "3.12"

232
Pipfile.lock generated Normal file
View File

@ -0,0 +1,232 @@
{
"_meta": {
"hash": {
"sha256": "7a239bffead5df2e0a422f7b84d0ee970f85f9d94ab29b1d56d004f67144422c"
},
"pipfile-spec": 6,
"requires": {
"python_version": "3.12"
},
"sources": [
{
"name": "pypi",
"url": "https://pypi.org/simple",
"verify_ssl": true
}
]
},
"default": {
"blinker": {
"hashes": [
"sha256:b4ce2265a7abece45e7cc896e98dbebe6cead56bcf805a3d23136d145f5445bf",
"sha256:ba0efaa9080b619ff2f3459d1d500c57bddea4a6b424b60a91141db6fd2f08bc"
],
"markers": "python_version >= '3.9'",
"version": "==1.9.0"
},
"click": {
"hashes": [
"sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28",
"sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"
],
"markers": "python_version >= '3.7'",
"version": "==8.1.7"
},
"flask": {
"hashes": [
"sha256:5f873c5184c897c8d9d1b05df1e3d01b14910ce69607a117bd3277098a5836ac",
"sha256:d667207822eb83f1c4b50949b1623c8fc8d51f2341d65f72e1a1815397551136"
],
"index": "pypi",
"markers": "python_version >= '3.9'",
"version": "==3.1.0"
},
"flask-sock": {
"hashes": [
"sha256:caac4d679392aaf010d02fabcf73d52019f5bdaf1c9c131ec5a428cb3491204a",
"sha256:e023b578284195a443b8d8bdb4469e6a6acf694b89aeb51315b1a34fcf427b7d"
],
"index": "pypi",
"markers": "python_version >= '3.6'",
"version": "==0.7.0"
},
"h11": {
"hashes": [
"sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d",
"sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"
],
"markers": "python_version >= '3.7'",
"version": "==0.14.0"
},
"itsdangerous": {
"hashes": [
"sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef",
"sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173"
],
"markers": "python_version >= '3.8'",
"version": "==2.2.0"
},
"jinja2": {
"hashes": [
"sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369",
"sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"
],
"markers": "python_version >= '3.7'",
"version": "==3.1.4"
},
"markupsafe": {
"hashes": [
"sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4",
"sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30",
"sha256:1225beacc926f536dc82e45f8a4d68502949dc67eea90eab715dea3a21c1b5f0",
"sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9",
"sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396",
"sha256:1a9d3f5f0901fdec14d8d2f66ef7d035f2157240a433441719ac9a3fba440b13",
"sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028",
"sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca",
"sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557",
"sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832",
"sha256:3169b1eefae027567d1ce6ee7cae382c57fe26e82775f460f0b2778beaad66c0",
"sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b",
"sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579",
"sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a",
"sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c",
"sha256:48032821bbdf20f5799ff537c7ac3d1fba0ba032cfc06194faffa8cda8b560ff",
"sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c",
"sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22",
"sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094",
"sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb",
"sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e",
"sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5",
"sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a",
"sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d",
"sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a",
"sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b",
"sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8",
"sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225",
"sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c",
"sha256:88b49a3b9ff31e19998750c38e030fc7bb937398b1f78cfa599aaef92d693144",
"sha256:8c4e8c3ce11e1f92f6536ff07154f9d49677ebaaafc32db9db4620bc11ed480f",
"sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87",
"sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d",
"sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93",
"sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf",
"sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158",
"sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84",
"sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb",
"sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48",
"sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171",
"sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c",
"sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6",
"sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd",
"sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d",
"sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1",
"sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d",
"sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca",
"sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a",
"sha256:cfad01eed2c2e0c01fd0ecd2ef42c492f7f93902e39a42fc9ee1692961443a29",
"sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe",
"sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798",
"sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c",
"sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8",
"sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f",
"sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f",
"sha256:eaa0a10b7f72326f1372a713e73c3f739b524b3af41feb43e4921cb529f5929a",
"sha256:eb7972a85c54febfb25b5c4b4f3af4dcc731994c7da0d8a0b4a6eb0640e1d178",
"sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0",
"sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79",
"sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430",
"sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50"
],
"markers": "python_version >= '3.9'",
"version": "==3.0.2"
},
"numpy": {
"hashes": [
"sha256:016d0f6f5e77b0f0d45d77387ffa4bb89816b57c835580c3ce8e099ef830befe",
"sha256:02135ade8b8a84011cbb67dc44e07c58f28575cf9ecf8ab304e51c05528c19f0",
"sha256:08788d27a5fd867a663f6fc753fd7c3ad7e92747efc73c53bca2f19f8bc06f48",
"sha256:0d30c543f02e84e92c4b1f415b7c6b5326cbe45ee7882b6b77db7195fb971e3a",
"sha256:0fa14563cc46422e99daef53d725d0c326e99e468a9320a240affffe87852564",
"sha256:13138eadd4f4da03074851a698ffa7e405f41a0845a6b1ad135b81596e4e9958",
"sha256:14e253bd43fc6b37af4921b10f6add6925878a42a0c5fe83daee390bca80bc17",
"sha256:15cb89f39fa6d0bdfb600ea24b250e5f1a3df23f901f51c8debaa6a5d122b2f0",
"sha256:17ee83a1f4fef3c94d16dc1802b998668b5419362c8a4f4e8a491de1b41cc3ee",
"sha256:2312b2aa89e1f43ecea6da6ea9a810d06aae08321609d8dc0d0eda6d946a541b",
"sha256:2564fbdf2b99b3f815f2107c1bbc93e2de8ee655a69c261363a1172a79a257d4",
"sha256:3522b0dfe983a575e6a9ab3a4a4dfe156c3e428468ff08ce582b9bb6bd1d71d4",
"sha256:4394bc0dbd074b7f9b52024832d16e019decebf86caf909d94f6b3f77a8ee3b6",
"sha256:45966d859916ad02b779706bb43b954281db43e185015df6eb3323120188f9e4",
"sha256:4d1167c53b93f1f5d8a139a742b3c6f4d429b54e74e6b57d0eff40045187b15d",
"sha256:4f2015dfe437dfebbfce7c85c7b53d81ba49e71ba7eadbf1df40c915af75979f",
"sha256:50ca6aba6e163363f132b5c101ba078b8cbd3fa92c7865fd7d4d62d9779ac29f",
"sha256:50d18c4358a0a8a53f12a8ba9d772ab2d460321e6a93d6064fc22443d189853f",
"sha256:5641516794ca9e5f8a4d17bb45446998c6554704d888f86df9b200e66bdcce56",
"sha256:576a1c1d25e9e02ed7fa5477f30a127fe56debd53b8d2c89d5578f9857d03ca9",
"sha256:6a4825252fcc430a182ac4dee5a505053d262c807f8a924603d411f6718b88fd",
"sha256:72dcc4a35a8515d83e76b58fdf8113a5c969ccd505c8a946759b24e3182d1f23",
"sha256:747641635d3d44bcb380d950679462fae44f54b131be347d5ec2bce47d3df9ed",
"sha256:762479be47a4863e261a840e8e01608d124ee1361e48b96916f38b119cfda04a",
"sha256:78574ac2d1a4a02421f25da9559850d59457bac82f2b8d7a44fe83a64f770098",
"sha256:825656d0743699c529c5943554d223c021ff0494ff1442152ce887ef4f7561a1",
"sha256:8637dcd2caa676e475503d1f8fdb327bc495554e10838019651b76d17b98e512",
"sha256:96fe52fcdb9345b7cd82ecd34547fca4321f7656d500eca497eb7ea5a926692f",
"sha256:973faafebaae4c0aaa1a1ca1ce02434554d67e628b8d805e61f874b84e136b09",
"sha256:996bb9399059c5b82f76b53ff8bb686069c05acc94656bb259b1d63d04a9506f",
"sha256:a38c19106902bb19351b83802531fea19dee18e5b37b36454f27f11ff956f7fc",
"sha256:a6b46587b14b888e95e4a24d7b13ae91fa22386c199ee7b418f449032b2fa3b8",
"sha256:a9f7f672a3388133335589cfca93ed468509cb7b93ba3105fce780d04a6576a0",
"sha256:aa08e04e08aaf974d4458def539dece0d28146d866a39da5639596f4921fd761",
"sha256:b0df3635b9c8ef48bd3be5f862cf71b0a4716fa0e702155c45067c6b711ddcef",
"sha256:b47fbb433d3260adcd51eb54f92a2ffbc90a4595f8970ee00e064c644ac788f5",
"sha256:baed7e8d7481bfe0874b566850cb0b85243e982388b7b23348c6db2ee2b2ae8e",
"sha256:bc6f24b3d1ecc1eebfbf5d6051faa49af40b03be1aaa781ebdadcbc090b4539b",
"sha256:c006b607a865b07cd981ccb218a04fc86b600411d83d6fc261357f1c0966755d",
"sha256:c181ba05ce8299c7aa3125c27b9c2167bca4a4445b7ce73d5febc411ca692e43",
"sha256:c7662f0e3673fe4e832fe07b65c50342ea27d989f92c80355658c7f888fcc83c",
"sha256:c80e4a09b3d95b4e1cac08643f1152fa71a0a821a2d4277334c88d54b2219a41",
"sha256:c894b4305373b9c5576d7a12b473702afdf48ce5369c074ba304cc5ad8730dff",
"sha256:d7aac50327da5d208db2eec22eb11e491e3fe13d22653dce51b0f4109101b408",
"sha256:d89dd2b6da69c4fff5e39c28a382199ddedc3a5be5390115608345dec660b9e2",
"sha256:d9beb777a78c331580705326d2367488d5bc473b49a9bc3036c154832520aca9",
"sha256:dc258a761a16daa791081d026f0ed4399b582712e6fc887a95af09df10c5ca57",
"sha256:e14e26956e6f1696070788252dcdff11b4aca4c3e8bd166e0df1bb8f315a67cb",
"sha256:e6988e90fcf617da2b5c78902fe8e668361b43b4fe26dbf2d7b0f8034d4cafb9",
"sha256:e711e02f49e176a01d0349d82cb5f05ba4db7d5e7e0defd026328e5cfb3226d3",
"sha256:ea4dedd6e394a9c180b33c2c872b92f7ce0f8e7ad93e9585312b0c5a04777a4a",
"sha256:ecc76a9ba2911d8d37ac01de72834d8849e55473457558e12995f4cd53e778e0",
"sha256:f55ba01150f52b1027829b50d70ef1dafd9821ea82905b63936668403c3b471e",
"sha256:f653490b33e9c3a4c1c01d41bc2aef08f9475af51146e4a7710c450cf9761598",
"sha256:fa2d1337dc61c8dc417fbccf20f6d1e139896a30721b7f1e832b2bb6ef4eb6c4"
],
"index": "pypi",
"markers": "python_version >= '3.10'",
"version": "==2.1.3"
},
"simple-websocket": {
"hashes": [
"sha256:4af6069630a38ed6c561010f0e11a5bc0d4ca569b36306eb257cd9a192497c8c",
"sha256:7939234e7aa067c534abdab3a9ed933ec9ce4691b0713c78acb195560aa52ae4"
],
"markers": "python_version >= '3.6'",
"version": "==1.1.0"
},
"werkzeug": {
"hashes": [
"sha256:54b78bf3716d19a65be4fceccc0d1d7b89e608834989dfae50ea87564639213e",
"sha256:60723ce945c19328679790e3282cc758aa4a6040e4bb330f53d30fa546d44746"
],
"markers": "python_version >= '3.9'",
"version": "==3.1.3"
},
"wsproto": {
"hashes": [
"sha256:ad565f26ecb92588a3e43bc3d96164de84cd9902482b130d0ddbaa9664a85065",
"sha256:b9acddd652b585d75b20477888c56642fdade28bdfd3579aa24a4d2c037dd736"
],
"markers": "python_full_version >= '3.7.0'",
"version": "==1.2.0"
}
},
"develop": {}
}

119
src/csi.py Normal file
View File

@ -0,0 +1,119 @@
import struct
import numpy as np
RATE_MCS_MOD_TYPE_POS = 8
RATE_MCS_MOD_TYPE_MSK = 0x7 << RATE_MCS_MOD_TYPE_POS
RATE_MCS_CCK_MSK = 0 << RATE_MCS_MOD_TYPE_POS
RATE_MCS_LEGACY_OFDM_MSK = 1 << RATE_MCS_MOD_TYPE_POS
RATE_MCS_HT_MSK = 2 << RATE_MCS_MOD_TYPE_POS
RATE_MCS_VHT_MSK = 3 << RATE_MCS_MOD_TYPE_POS
RATE_MCS_HE_MSK = 4 << RATE_MCS_MOD_TYPE_POS
RATE_MCS_EHT_MSK = 5 << RATE_MCS_MOD_TYPE_POS
RATE_MCS_CHAN_WIDTH_POS = 11
RATE_MCS_CHAN_WIDTH_MSK = 0x7 << RATE_MCS_CHAN_WIDTH_POS
RATE_MCS_CHAN_WIDTH_20_VAL = 0
RATE_MCS_CHAN_WIDTH_20 = RATE_MCS_CHAN_WIDTH_20_VAL << RATE_MCS_CHAN_WIDTH_POS
RATE_MCS_CHAN_WIDTH_40_VAL = 1
RATE_MCS_CHAN_WIDTH_40 = RATE_MCS_CHAN_WIDTH_40_VAL << RATE_MCS_CHAN_WIDTH_POS
RATE_MCS_CHAN_WIDTH_80_VAL = 2
RATE_MCS_CHAN_WIDTH_80 = RATE_MCS_CHAN_WIDTH_80_VAL << RATE_MCS_CHAN_WIDTH_POS
RATE_MCS_CHAN_WIDTH_160_VAL = 3
RATE_MCS_CHAN_WIDTH_160 = RATE_MCS_CHAN_WIDTH_160_VAL << RATE_MCS_CHAN_WIDTH_POS
RATE_MCS_CHAN_WIDTH_320_VAL = 4
RATE_MCS_CHAN_WIDTH_320 = RATE_MCS_CHAN_WIDTH_320_VAL << RATE_MCS_CHAN_WIDTH_POS
RATE_HT_MCS_CODE_MSK = 7
RATE_MCS_ANT_A_POS = 14
RATE_MCS_ANT_A_MSK = 1 << RATE_MCS_ANT_A_POS
RATE_MCS_ANT_B_POS = 15
RATE_MCS_ANT_B_MSK = 1 << RATE_MCS_ANT_B_POS
RATE_MCS_LDPC_POS = 16
RATE_MCS_LDPC_MSK = 1 << RATE_MCS_LDPC_POS
RATE_MCS_SS_POS = 16
RATE_MCS_SS_MSK = 1 << RATE_MCS_SS_POS
RATE_MCS_BEAMF_POS = 16
RATE_MCS_BEAMF_MSK = 1 << RATE_MCS_BEAMF_POS
class CSIHeader:
def __init__(self, data: bytes):
self.csi_size = struct.unpack("I", data[0:4])[0]
self.ftm_clock = struct.unpack("I", data[8:12])[0]
self.num_rx = data[46]
self.num_tx = data[47]
self.num_subcarriers = struct.unpack("I", data[52:56])[0]
self.rssi1 = struct.unpack("I", data[60:64])[0]
self.rssi2 = struct.unpack("I", data[64:68])[0]
self.source_mac = struct.unpack("BBBBBB", data[68:74])
self.source_mac_string = "%02x:%02x:%02x:%02x:%02x:%02x" % struct.unpack(
"BBBBBB", data[68:74]
)
self.rate_flags = struct.unpack("I", data[92:96])[0]
rate_format = self.rate_flags & RATE_MCS_MOD_TYPE_MSK
if rate_format == RATE_MCS_CCK_MSK:
rate_format = "CCK"
elif rate_format == RATE_MCS_LEGACY_OFDM_MSK:
rate_format = "LEGACY_OFDM"
elif rate_format == RATE_MCS_VHT_MSK:
rate_format = "VHT"
elif rate_format == RATE_MCS_HT_MSK:
rate_format = "HT"
elif rate_format == RATE_MCS_HE_MSK:
rate_format = "HE"
elif rate_format == RATE_MCS_EHT_MSK:
rate_format = "EHT"
else:
rate_format = "unknown"
self.rate_format = rate_format
channel_width = self.rate_flags & RATE_MCS_CHAN_WIDTH_MSK
if channel_width == RATE_MCS_CHAN_WIDTH_20:
channel_width = "20"
elif channel_width == RATE_MCS_CHAN_WIDTH_40:
channel_width = "40"
elif channel_width == RATE_MCS_CHAN_WIDTH_80:
channel_width = "80"
elif channel_width == RATE_MCS_CHAN_WIDTH_160:
channel_width = "160"
elif channel_width == RATE_MCS_CHAN_WIDTH_320:
channel_width = "320"
else:
channel_width = "unknown"
self.channel_width = channel_width
self.mcs = self.rate_flags & RATE_HT_MCS_CODE_MSK
self.antenna_a = True if self.rate_flags & RATE_MCS_ANT_A_MSK else False
self.antenna_b = True if self.rate_flags & RATE_MCS_ANT_B_MSK else False
self.ldpc = True if self.rate_flags & RATE_MCS_LDPC_MSK else False
self.ss = 2 if self.rate_flags & RATE_MCS_SS_MSK else 1
self.beamforming = True if self.rate_flags & RATE_MCS_BEAMF_MSK else False
class CSI:
@staticmethod
def parseCsiData(data: bytes, header: CSIHeader):
csi_matrix = np.zeros(
(
header.num_subcarriers,
header.num_rx,
header.num_tx,
),
dtype=complex,
)
pos = 0
for j in range(header.num_rx):
for k in range(header.num_tx):
for n in range(header.num_subcarriers):
real = struct.unpack("h", data[pos : pos + 2])[0]
imag = struct.unpack("h", data[pos + 2 : pos + 4])[0]
pos += 4
csi_matrix[n, j, k] = complex(real, imag)
return csi_matrix
def __init__(self, data: bytes):
self.header = CSIHeader(data[:272])
self.matrix = self.parseCsiData(
data[272 : 272 + self.header.csi_size], self.header
)

59
src/main.py Normal file
View File

@ -0,0 +1,59 @@
import logging
import socket
from csi import CSI
from preprocess import Preprocessor
import visualise
import threading
import struct
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s %(levelname)-8s %(message)s",
)
class FeitServer:
def __init__(
self,
ip: str,
frequency: int = 5260,
channel_width: int = 80,
frame_format: str = "VHT",
):
self.ip = ip
self.port = 8008
self.server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.server.connect((self.ip, self.port))
self.start_string = (
f"feitcsi --frequency {frequency} "
f"--channel-width {channel_width} --format {frame_format} "
f"--mode measure"
)
self.server.send(b"stop\n")
self.server.send(self.start_string.encode())
def listen(self, callback):
while True:
data = self.server.recv(65535)
try:
csidata = CSI(data)
callback(csidata)
except struct.error:
logging.error("Failed to parse CSI data")
def process_data(data):
logging.debug("Got CSI frame")
processed = preprocess.preprocess(data)
visualise.add_data(processed)
if __name__ == "__main__":
server = FeitServer("10.0.12.62")
preprocess = Preprocessor()
# Start webapp in background thread
webapp = threading.Thread(target=visualise.start)
webapp.start()
server.listen(process_data)

41
src/preprocess.py Normal file
View File

@ -0,0 +1,41 @@
import numpy as np
from csi import CSI
import logging
from queue import Queue
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)
class Preprocessor:
NUM_SHORT_TERM_ENTRIES = 10
LONG_TERM_ALPHA = 0.1
def __init__(self):
self.prev_entries = Queue(maxsize=100)
self.short_term_avg = np.zeros((1,), dtype=complex)
self.long_term_avg = np.zeros((1,), dtype=complex)
def preprocess(self, csi: CSI):
h = csi.matrix
h_hat = np.multiply(h, h.conj() / abs(h.conj()))
# Assume that all csi matrices will have the same shape
if self.short_term_avg.shape != h_hat.shape:
self.short_term_avg = np.zeros(h_hat.shape, dtype=complex)
self.long_term_avg = np.zeros(h_hat.shape, dtype=complex)
self.prev_entries = Queue(maxsize=100)
while self.prev_entries.full():
old_value = self.prev_entries.get()
self.short_term_avg -= old_value / self.NUM_SHORT_TERM_ENTRIES
self.prev_entries.put(h_hat)
self.short_term_avg += h_hat / self.NUM_SHORT_TERM_ENTRIES
self.long_term_avg = (
self.long_term_avg * (1 - self.LONG_TERM_ALPHA)
+ self.short_term_avg * self.LONG_TERM_ALPHA
)
# Remove static components
current_measurement = self.short_term_avg - self.long_term_avg
return current_measurement

53
src/visualise/__init__.py Normal file
View File

@ -0,0 +1,53 @@
from flask import Flask, render_template, request
from flask_sock import Sock
import numpy as np
import numpy.typing as npt
import json
app = Flask(__name__)
sock = Sock(app)
data: npt.NDArray[np.complex128] = np.array([], dtype=complex)
@app.route("/preprocessed")
def preprocessed():
return render_template("preprocessed.html")
subscribers = {}
@sock.route("/data")
def get_data(sock):
while True:
msg = sock.receive()
subscribers[sock] = list(map(int, msg.split()))
def add_data(new_data):
global data
if data.size == 0:
data = np.expand_dims(new_data, axis=0)
else:
data = np.concat([data, np.expand_dims(new_data, axis=0)], axis=0)
# Only keep latest 100 entries
if data.shape[0] > 100:
data = data[-100:]
to_remove = []
for subscriber in subscribers:
try:
subcarrier, rx, tx = subscribers[subscriber]
subscriber.send(new_data.real[subcarrier, rx, tx])
except Exception as e:
to_remove.append(subscriber)
print(e)
for subscriber in to_remove:
del subscribers[subscriber]
def start():
app.run(debug=True, use_reloader=False)

View File

@ -0,0 +1,42 @@
const plotElement = document.getElementById("plot");
const layout = {
title: "Real-Time Complex Data Visualization",
xaxis: { title: "Time (ms)" },
yaxis: { title: "Value" },
};
Plotly.newPlot(
plotElement,
[
{
x: [],
y: [],
type: "scatter", // Line chart
mode: "lines+markers", // Line + markers
},
],
layout,
);
const socket = new WebSocket("ws://" + location.host + "/data");
socket.onopen = function () {
console.log("Connected to the server");
socket.send("0 0 0");
};
socket.addEventListener("message", function (msg) {
Plotly.extendTraces(
plotElement,
{
x: [[msg.timeStamp]],
y: [[msg.data]],
},
[0],
100,
);
});
function changeSubcarrier() {
const subcarrier = document.getElementById("subcarrier").value;
socket.send(subcarrier + " 0 0");
}

View File

@ -0,0 +1,15 @@
<!DOCTYPE html>
<html>
<head>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
<script src="https://cdn.plot.ly/plotly-2.35.2.min.js" charset="utf-8"></script>
</head>
<body>
<div class="container">
<h1>Preprocessing</h1>
<div id="plot"></div>
<script src="{{url_for('static', filename='plot.js')}}"></script>
<input type="number" id="subcarrier" class="form-control" placeholder="Choose subcarrier" onchange="changeSubcarrier()" value="0">
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
</body>