├── .gitignore
├── .gitmodules
├── .idea
├── .gitignore
├── PicoToolBox.iml
├── inspectionProfiles
│ └── profiles_settings.xml
├── misc.xml
├── modules.xml
├── other.xml
└── vcs.xml
├── LICENSE.md
├── README.md
├── docs
├── Figure_1.png
└── PicoToolBoxdoc.md
├── main.py
├── picoscenes.pyx
├── pyproject.toml
├── rx_by_usrpN210.csi
├── setup.py
└── test.py
/.gitignore:
--------------------------------------------------------------------------------
1 | *.csi
2 | .DS_Store
3 | !rx_by_usrpN210.csi
4 | picoscenes.cpp
5 | # Byte-compiled / optimized / DLL files
6 | __pycache__/
7 | *.py[cod]
8 | *$py.class
9 |
10 | localtest.py
11 |
12 |
13 | # C extensions
14 | *.so
15 |
16 | # Distribution / packaging
17 | .Python
18 | build/
19 | develop-eggs/
20 | dist/
21 | downloads/
22 | eggs/
23 | .eggs/
24 | lib/
25 | lib64/
26 | parts/
27 | sdist/
28 | var/
29 | wheels/
30 | share/python-wheels/
31 | *.egg-info/
32 | .installed.cfg
33 | *.egg
34 | MANIFEST
35 |
36 | # PyInstaller
37 | # Usually these files are written by a python script from a template
38 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
39 | *.manifest
40 | *.spec
41 |
42 | # Installer logs
43 | pip-log.txt
44 | pip-delete-this-directory.txt
45 |
46 | # Unit test / coverage reports
47 | htmlcov/
48 | .tox/
49 | .nox/
50 | .coverage
51 | .coverage.*
52 | .cache
53 | nosetests.xml
54 | coverage.xml
55 | *.cover
56 | *.py,cover
57 | .hypothesis/
58 | .pytest_cache/
59 | cover/
60 |
61 | # Translations
62 | *.mo
63 | *.pot
64 |
65 | # Django stuff:
66 | *.log
67 | local_settings.py
68 | db.sqlite3
69 | db.sqlite3-journal
70 |
71 | # Flask stuff:
72 | instance/
73 | .webassets-cache
74 |
75 | # Scrapy stuff:
76 | .scrapy
77 |
78 | # Sphinx documentation
79 | docs/_build/
80 |
81 | # PyBuilder
82 | .pybuilder/
83 | target/
84 |
85 | # Jupyter Notebook
86 | .ipynb_checkpoints
87 |
88 | # IPython
89 | profile_default/
90 | ipython_config.py
91 |
92 | # pyenv
93 | # For a library or package, you might want to ignore these files since the code is
94 | # intended to run in multiple environments; otherwise, check them in:
95 | # .python-version
96 |
97 | # pipenv
98 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
99 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
100 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
101 | # install all needed dependencies.
102 | #Pipfile.lock
103 |
104 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow
105 | __pypackages__/
106 |
107 | # Celery stuff
108 | celerybeat-schedule
109 | celerybeat.pid
110 |
111 | # SageMath parsed files
112 | *.sage.py
113 |
114 | # Environments
115 | .env
116 | .venv
117 | env/
118 | venv/
119 | ENV/
120 | env.bak/
121 | venv.bak/
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 | # Cython debug symbols
145 | cython_debug/
146 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "rxs_parsing_core"]
2 | path = rxs_parsing_core
3 | url = https://github.com/wifisensing/RXS-Parsing-Core.git
4 |
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 | # Datasource local storage ignored files
5 | /dataSources/
6 | /dataSources.local.xml
7 | # Editor-based HTTP Client requests
8 | /httpRequests/
9 |
--------------------------------------------------------------------------------
/.idea/PicoToolBox.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/other.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2021 Tian Teng
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
6 | documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
7 | rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
8 | persons to whom the Software is furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
11 | Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
14 | WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
15 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
16 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
17 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Picoscenes Python Toolbox
2 |
3 | The official Python toolbox for parsing **.csi** files from PicoScenes.
4 |
5 | ## Highlights
6 |
7 | - Supports Linux, macOS, and Windows
8 | - Easy to use
9 | - Faster parsing than the MATLAB implementation
10 | - High scalability
11 | - The official toolbox for parsing **.csi** files
12 |
13 | ## Preparation on Windows 10 or 11
14 |
15 | 1. Install [TDM-GCC-64](https://jmeubank.github.io/tdm-gcc/) (choose the MinGW-w64-based version, version 10.3+).
16 | 2. Set the compiler to MinGW64 using [mingw64ccompiler](https://github.com/imba-tjd/mingw64ccompiler):
17 |
18 | ```bash
19 | pip install git+https://github.com/imba-tjd/mingw64ccompiler
20 | python -m mingw64ccompiler install_specs # Run once
21 | python -m mingw64ccompiler install # Works with venv
22 | ```
23 |
24 | ## Installation
25 |
26 | 1. Clone this repository with the `--recursive` option:
27 |
28 | ```bash
29 | git clone https://github.com/Herrtian/PicoscenesToolbox.git --recursive
30 | ```
31 |
32 | 2. Install Python and dependencies:
33 |
34 | ```bash
35 | sudo apt update
36 | sudo apt install python3 python3-pip python3-venv
37 | ```
38 |
39 | - If you are a **Chinese** user, you can change the pip source to accelerate the download speed:
40 |
41 | ```bash
42 | pip config set global.index-url https://mirrors.aliyun.com/pypi/simple/
43 | ```
44 |
45 | Preferrably, create a virtual environment
46 |
47 | ```bash
48 | python3 -m venv .venv
49 | source .venv/bin/activate # or run venv\Scripts\activate on Windows
50 | ```
51 |
52 | 3. Build the package
53 |
54 | ```bash
55 | pip3 install . # add -e for an editable; so no reinstall is required on changes
56 | ```
57 |
58 | ## Quick start
59 |
60 | **rx_by_usrpN210.csi** is a sample binary csi file created by **Picoscenes**.
61 | Refer to `main.py` for an example usage on how to parse and plot the collected CSI.
62 | This example shows the first frame of **rx_by_usrpN210.csi** and creates a plot
63 | with x-axis representing **SubcarrierIndex** and y-axis showing **Magnitude**.
64 |
65 | ```bash
66 | python3 main.py
67 | ```
68 |
69 | On a successful run, you should see an output similar to:
70 |
71 | 
72 |
73 | ## Documentation
74 |
75 | For more details about **PicoscenesToolbox**, refer to the **./docs** folder or visit the [wiki]().
76 |
77 | ## Issues & Feedback
78 |
79 | If you encounter any issues or have questions about **PicoscenesToolbox**, please submit an **issue** on GitHub.
80 |
81 | Consider giving this project a **star** or **fork** if you find it useful!
82 |
83 | ## References
84 |
85 | - **[PicoScenes](https://ps.zpj.io/)**: A powerful Wi-Fi sensing platform middleware for a wide range of hardware.
86 | - This project was released by [Zhiping Jiang](https://zpj.io/bio/).
87 | - [**csiread**](https://github.com/citysu/csiread): A fast channel state information parser for Intel, Atheros, Nexmon, and ESP32 in Python.
88 | - This project, developed by [citysu](https://github.com/citysu/csiread), served as inspiration for **PicoscenesToolbox**.
89 |
90 | ## License
91 |
92 | This project is licensed under the **MIT License**. If you use this toolbox in your project, citing this repository would be greatly appreciated.
93 |
94 | ```
95 | Tian Teng. PicoscenesToolbox: An official tool plugin for parsing **.csi** from PicoScenes in Python. (2021). https://github.com/Herrtian/PicoscenesToolbox.
96 |
--------------------------------------------------------------------------------
/docs/Figure_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wifisensing/PicoScenes-Python-Toolbox/f676855accedf79e848bf2f1d10ee5c1f649a33a/docs/Figure_1.png
--------------------------------------------------------------------------------
/docs/PicoToolBoxdoc.md:
--------------------------------------------------------------------------------
1 | # PicoToolBox
2 |
3 | This docs shows you how the datastructure displays in PicoToolBox.
4 |
5 | You will not know how the python parse CSI data,but you would better know the structure if you want to create your own data analysis.
6 |
7 | ## Picoscenes
8 |
9 | Picoscenes is the well packed frames ,you can use **.raw**[] to get a specific frame.
10 |
11 | Example:
12 |
13 | ```python
14 | i = 0
15 | frames = Picoscenes("rx_by_usrpN210.csi") # return a Picoscenes Object
16 | First_Frame = frames.raw[i] # get the first frame ,you can set i to get different frame
17 | ```
18 |
19 |
20 |
21 | Now i will show you the more details in Picoscenes raw.
22 |
23 | For all the attributes i will give you a small example.
24 |
25 | ### Frame
26 |
27 | frame is a structure which inlcudes all information and structure .
28 |
29 | you can also use **print(frames.raw[0])** to get the newst infomation if i have not updated the docs.
30 |
31 | frame has the method frame_value_xxx_has_value it means some attributes are **optional**
32 |
33 | For example, if **frame_value.mvmExtraSegment.has_value** == 1 ,then **frame.raw[0].MVMExtra** is a concrete value, or it is **None**
34 |
35 | | Name | Type | Illustration |
36 | | ---------------- | -------------------------- | ------------ |
37 | | StandardHeader | ieee80211_mac_frame_header | |
38 | | RxSBasic | RxSBasicSegment | |
39 | | RxExtraInfo | ExtraInfoSegment | |
40 | | CSI | CSISegment | |
41 | | MVMExtra | MVMExtraSegment | optional |
42 | | PicoScenesHeader | PicoScenesFrameHeader | optional |
43 | | TxExtraInfo | ExtraInfoSegment | optional |
44 | | PilotCSI | CSISegment | optional |
45 | | LegacyCSI | CSISegment | optional |
46 | | BasebandSignals | BasebandSignalSegment | optional |
47 | | PreEQSymbols | PreEQSymbolsSegment | optional |
48 | | MPDU | vector[uint8_t] | |
49 |
50 |
51 |
52 | **note**: **StandardHeader** & **CSI** & **RxSBasic** & **RxExtraInfo** are all the strcutures contained.
53 |
54 |
55 |
56 | ### CSI
57 |
58 | CSI is the most important structure in Picoscenes.
59 |
60 | | Name | Type | Illustration |
61 | | ------------------- | ------------------------------ | -------------- |
62 | | DeviceType | PicoScenesDeviceType | uint16_t |
63 | | PacketFormat | PacketFormatEnum | int8_t |
64 | | CBW | ChannelBandwidthEnum | uint16_t |
65 | | CarrierFreq | uint64_t | |
66 | | SamplingRate | uint64_t | |
67 | | SubcarrierBandwidth | uint32_t | |
68 | | numTones | CSIDimension | uint16_t |
69 | | numTx | CSIDimension | uint8_t |
70 | | numRx | CSIDimension | uint8_t |
71 | | numESS | CSIDimension | uint8_t |
72 | | numCSI | CSIDimension | uint16_t |
73 | | ant_sel | uint8_t | |
74 | | CSI | SignalMatrix[ccomplex[double]] | Cplx structure |
75 | | Mag | SignalMatrix[double] | Cplx structure |
76 | | Phase | SignalMatrix[double] | Cplx structure |
77 | | SubcarrierIndex | vector[int16_t] | Cplx structure |
78 |
79 | **uint** means **unsigned int**
80 |
81 | #### Example
82 |
83 | ```python
84 | i = 0
85 | frames = Picoscenes("rx_by_usrpN210.csi")
86 | numTones = frames.raw[i].get("CSI").get("numTones") # get the first frame numtones
87 | ```
88 |
89 |
90 |
91 | #### PicoScenesDeviceType (uint16_t)
92 |
93 | # all attributes are uint16_t
94 | QCA9300
95 | IWL5300
96 | IWLMVM
97 | MAC80211Compatible
98 | USRP
99 | VirtualSDR
100 | Unknown
101 |
102 |
103 |
104 | #### PacketFormatEnum(int8_t)
105 |
106 | PacketFormat_NonHT
107 | PacketFormat_HT
108 | PacketFormat_VHT
109 | PacketFormat_HESU
110 | PacketFormat_HEMU
111 | PacketFormat_Unknown
112 |
113 |
114 |
115 | #### ChannelBandwidthEnum(uint16_t)
116 |
117 | CBW_5
118 | CBW_10
119 | CBW_20
120 | CBW_40
121 | CBW_80
122 | CBW_160
123 |
124 | #### SignalMatrix
125 |
126 | | Name | Type | Illustratio |
127 | | ---------- | --------------- | ----------- |
128 | | array | vector[T] | |
129 | | dimensions | vector[int64_t] | |
130 |
131 |
132 |
133 | #### CSIDimension
134 |
135 | uint16_t numTones
136 | uint8_t numTx
137 | uint8_t numRx
138 | uint8_t numESS
139 | uint16_t numCSI
140 |
141 | ### StandardHeader
142 |
143 | | Name | Type | Illustration |
144 | | ------------ | -------------------------- | -------------- |
145 | | ControlField | ieee80211_mac_frame_header | Cplx Structure |
146 | | Addr1 | uint8_t [6] | |
147 | | Addr2 | uint8_t [6] | |
148 | | Addr3 | uint8_t [6] | |
149 | | Fragment | uint16_t | |
150 | | Sequence | uint16_t | |
151 |
152 | #### Example
153 |
154 | ```python
155 | i = 0 # stands for the first frame of csi frames
156 |
157 | frames = Picoscenes("rx_by_usrpN210.csi")
158 | Fragment = frames.raw[i].get("StandardHeader").get("Fragment")
159 | ```
160 |
161 |
162 |
163 | #### ieee80211_mac_frame_header
164 |
165 | | Name | Type | Illustration |
166 | | ---------- | -------- | ------------ |
167 | | version | uint16_t | |
168 | | type | uint16_t | |
169 | | subtype | uint16_t | |
170 | | toDS | uint16_t | |
171 | | fromDS | uint16_t | |
172 | | moreFrags | uint16_t | |
173 | | retry | uint16_t | |
174 | | power_mgmt | uint16_t | |
175 | | more | uint16_t | |
176 | | protect | uint16_t | |
177 | | order | uint16_t | |
178 |
179 |
180 |
181 | ### RxSBasic
182 |
183 | | Name | Type | Illustration |
184 | | ------------ | -------- | ------------ |
185 | | deviceType | uint16_t | |
186 | | timestamp | uint64_t | |
187 | | centerFreq | int16_t | |
188 | | controlFreq | int16_t | |
189 | | CBW | uint16_t | |
190 | | packetFormat | uint8_t | |
191 | | packetCBW | uint16_t | |
192 | | GI | uint16_t | |
193 | | MCS | uint8_t | |
194 | | numSTS | uint8_t | |
195 | | numESS | uint8_t | |
196 | | numRx | uint8_t | |
197 | | noiseFloor | int8_t | |
198 | | rssi | int8_t | |
199 | | rssi1 | int8_t | |
200 | | rssi2 | int8_t | |
201 | | rssi3 | int8_t | |
202 |
203 |
204 |
205 | #### Example
206 |
207 | ```python
208 | i = 0 # stands for the first frame of csi frames
209 |
210 | frames = Picoscenes("rx_by_usrpN210.csi")
211 | timestamp = frames.raw[i].get("RxSBasic").get("timestamp")
212 |
213 | ```
214 |
215 |
216 |
217 | ### RxExtraInfo(Optional)
218 |
219 | Extrainfo is also a cplx flexible structure. It contains the **has_xxx** attribute.
220 |
221 | **has_xxx** is a **bint** type, which is 0 or 1.
222 |
223 | For example, if **hasLength** == 1 ,then **RxExtraInfo.Length** is a concrete value, or it is **None**
224 |
225 | In order to **not** trouble you ,i will just list the meaningful Information of the RXExtraInfo.
226 |
227 |
228 |
229 | | Name | Type | Illustration |
230 | | ---------------- | --------------------- | -------------- |
231 | | length | uint16_t | |
232 | | version | uint64_t | |
233 | | macaddr_cur | uint8_t [6] | |
234 | | macaddr_rom | uint8_t [6] | |
235 | | chansel | uint32_t | |
236 | | bmode | uint8_t | |
237 | | evm | int8_t [20] | |
238 | | tx_chainmask | uint8_t | |
239 | | rx_chainmask | uint8_t | |
240 | | txpower | uint8_t | |
241 | | cf | uint64_t | |
242 | | txtsf | uint32_t | |
243 | | last_txtsf | uint32_t | |
244 | | channel_flags | uint16_t | |
245 | | tx_ness | uint8_t | |
246 | | tuning_policy | AtherosCFTuningPolicy | Cplx structure |
247 | | pll_rate | uint16_t | |
248 | | pll_clock_select | uint8_t | |
249 | | pll_refdiv | uint8_t | |
250 | | agc | uint8_t | |
251 | | ant_sel | uint8_t [3] | |
252 | | cfo | int32_t | |
253 | | sfo | int32_t | |
254 |
255 |
256 |
257 | #### Example
258 |
259 | ```python
260 | i = 0 # stands for the first frame of csi frames
261 |
262 | frames = Picoscenes("rx_by_usrpN210.csi")
263 | if frames.raw[i].get("RxExtraInfo").get("has_txpower"):
264 | txpower = frames.raw[i].get("RxExtraInfo").get("txpower")
265 | # make sure the frame has_xxx attribute then assign the variable
266 |
267 | ```
268 |
269 |
270 |
271 | #### AtherosCFTuningPolicy
272 |
273 | | Name | Type | Illustration |
274 | | ----------------------- | ------- | ------------ |
275 | | CFTuningByDefault | uint8_t | |
276 | | CFTuningByChansel | uint8_t | |
277 | | CFTuningByFastCC | uint8_t | |
278 | | CFTuningByHardwareReset | uint8_t | |
279 |
280 |
281 |
282 | ### MVMExtra(Optional)
283 |
284 | | Name | Type | Illustration |
285 | | ---------- | -------- | ------------ |
286 | | FMTClock | uint32_t | |
287 | | usClock | uint32_t | |
288 | | RateNFlags | uint32_t | |
289 |
290 |
291 |
292 | ### PicoScenesHeader(Optional)
293 |
294 | | Name | Type | Illustration |
295 | | ---------- | -------------------- | ------------ |
296 | | MagicValue | uint32_t | |
297 | | Version | uint32_t | |
298 | | DeviceType | PicoScenesDeviceType | uint16_t |
299 | | FrameType | uint8_t | |
300 | | TaskId | uint16_t | |
301 | | TxId | uint16_t | |
302 |
303 |
304 |
305 | ### TxExtraInfo(Optional)
306 |
307 | Txtrainfo is also a cplx flexible structure. It contains the **has_xxx** attribute.
308 |
309 | **has_xxx** is a **bint** type, which is 0 or 1.
310 |
311 | For example, if **hasLength** == 1 ,then **Txtrainfo .Length** is a concrete value, or it is **None**
312 |
313 | In order to **not** trouble you ,i will just list the meaningful Information of the Txtrainfo .
314 |
315 | | Name | Type | Illustration |
316 | | ---------------- | --------------------- | -------------- |
317 | | length | uint16_t | |
318 | | version | uint64_t | |
319 | | macaddr_cur | uint8_t [6] | |
320 | | macaddr_rom | uint8_t [6] | |
321 | | chansel | uint32_t | |
322 | | bmode | uint8_t | |
323 | | evm | int8_t [20] | |
324 | | tx_chainmask | uint8_t | |
325 | | rx_chainmask | uint8_t | |
326 | | txpower | uint8_t | |
327 | | cf | uint64_t | |
328 | | txtsf | uint32_t | |
329 | | last_txtsf | uint32_t | |
330 | | channel_flags | uint16_t | |
331 | | tx_ness | uint8_t | |
332 | | tuning_policy | AtherosCFTuningPolicy | Cplx structure |
333 | | pll_rate | uint16_t | |
334 | | pll_clock_select | uint8_t | |
335 | | pll_refdiv | uint8_t | |
336 | | agc | uint8_t | |
337 | | ant_sel | uint8_t [3] | |
338 | | cfo | int32_t | |
339 | | sfo | int32_t | |
340 |
341 |
342 |
343 | #### Example
344 |
345 | ```python
346 | i = 0 # stands for the first frame of csi frames
347 |
348 | frames = Picoscenes("rx_by_qca9300.csi")
349 | txpower = frames.raw[i].get("TxExtraInfo").get("txpower")
350 | print(txpower)
351 |
352 | ```
353 |
354 |
355 |
356 | ### PilotCSI(Optional)
357 |
358 | | Name | Type | Illustration |
359 | | ------------------- | ------------------------------ | -------------- |
360 | | DeviceType | PicoScenesDeviceType | uint16_t |
361 | | PacketFormat | PacketFormatEnum | int8_t |
362 | | CBW | ChannelBandwidthEnum | uint16_t |
363 | | CarrierFreq | uint64_t | |
364 | | SamplingRate | uint64_t | |
365 | | SubcarrierBandwidth | uint32_t | |
366 | | numTones | CSIDimension | uint16_t |
367 | | numTx | CSIDimension | uint8_t |
368 | | numRx | CSIDimension | uint8_t |
369 | | numESS | CSIDimension | uint8_t |
370 | | numCSI | CSIDimension | uint16_t |
371 | | ant_sel | uint8_t | |
372 | | CSI | SignalMatrix[ccomplex[double]] | Cplx structure |
373 | | Mag | SignalMatrix[double] | Cplx structure |
374 | | Phase | SignalMatrix[double] | Cplx structure |
375 | | SubcarrierIndex | vector[int16_t] | Cplx structure |
376 |
377 |
378 |
379 | #### Example
380 |
381 | ```python
382 | i = 0
383 | frames = Picoscenes("rx_by_usrpN210.csi")
384 | frame = frames.raw[i]
385 | if frame.get("PilotCSI"):
386 | numTones = frame.get("PilotCSI").get("numTones")
387 | ```
388 |
389 |
390 |
391 |
392 |
393 | ### LegacyCSI(Optional)
394 |
395 | | Name | Type | Illustration |
396 | | ------------------- | ------------------------------ | -------------- |
397 | | DeviceType | PicoScenesDeviceType | uint16_t |
398 | | PacketFormat | PacketFormatEnum | int8_t |
399 | | CBW | ChannelBandwidthEnum | uint16_t |
400 | | CarrierFreq | uint64_t | |
401 | | SamplingRate | uint64_t | |
402 | | SubcarrierBandwidth | uint32_t | |
403 | | numTones | CSIDimension | uint16_t |
404 | | numTx | CSIDimension | uint8_t |
405 | | numRx | CSIDimension | uint8_t |
406 | | numESS | CSIDimension | uint8_t |
407 | | numCSI | CSIDimension | uint16_t |
408 | | ant_sel | uint8_t | |
409 | | CSI | SignalMatrix[ccomplex[double]] | Cplx structure |
410 | | Mag | SignalMatrix[double] | Cplx structure |
411 | | Phase | SignalMatrix[double] | Cplx structure |
412 | | SubcarrierIndex | vector[int16_t] | Cplx structure |
413 |
414 |
415 |
416 | #### Example
417 |
418 | ```python
419 | i = 0
420 | frames = Picoscenes("rx_by_usrpN210.csi")
421 | frame = frames.raw[i]
422 | if frame.get("LegacyCSI"):
423 | numTones = frame.get("LegacyCSI").get("numTones")
424 | ```
425 |
426 |
427 |
428 | ### BasebandSignals(Optional[np.asarray])
429 |
430 | ```python
431 | i = 0
432 | frames = Picoscenes("rx_by_usrpN210.csi")
433 | frame = frames.raw[i]
434 | BasebandSignals = frame.get("BasebandSignals")
435 | ```
436 |
437 |
438 |
439 | ### PreEQSymbols(Optional[np.asarray])
440 |
441 | ```python
442 | i = 0
443 | frames = Picoscenes("rx_by_usrpN210.csi")
444 | frame = frames.raw[i]
445 | BasebandSignals = frame.get("PreEQSymbols")
446 | ```
447 |
448 |
449 |
450 | ### MPDU(uint8_t)
451 |
452 | ```python
453 | i = 0
454 | frames = Picoscenes("rx_by_usrpN210.csi")
455 | frame = frames.raw[i] # get the first frame numtones
456 | MPDU = frame.get("MPDU")
457 | ```
458 |
459 | **If you have something dont know , you can check picoscenes.pyx or contact with me. **
460 |
461 |
462 |
463 |
464 |
465 |
466 |
467 |
468 |
469 |
470 |
471 |
472 |
473 |
474 |
475 |
476 |
477 |
--------------------------------------------------------------------------------
/main.py:
--------------------------------------------------------------------------------
1 | """
2 | Simple usage example
3 |
4 | Processing and plotting CSI collected using Picoscenes with a USRP N210.
5 | """
6 | from picoscenes import Picoscenes
7 | import numpy as np
8 | import matplotlib.pyplot as plt
9 |
10 |
11 | frames = Picoscenes("rx_by_usrpN210.csi")
12 | frame = frames.raw[0] # Get the first frame in the list
13 |
14 | num_tones = frame.get("CSI").get("numTones")
15 | subcarrier_indices = np.array(frame.get("CSI").get("SubcarrierIndex"))
16 | magnitude = np.array(frame.get("CSI").get("Mag"))[:num_tones]
17 |
18 | plt.title("Magnitude Demo")
19 | plt.xlabel("subcarrier index")
20 | plt.ylabel("CSI Magnitude")
21 | plt.plot(subcarrier_indices, magnitude)
22 | plt.show()
23 |
--------------------------------------------------------------------------------
/picoscenes.pyx:
--------------------------------------------------------------------------------
1 | # distutils: language = c++
2 | import struct
3 |
4 | from libc.stdio cimport (fopen, fread, fclose, fseek, ftell, printf, FILE,
5 | SEEK_END, SEEK_SET, SEEK_CUR)
6 | from libcpp.memory cimport shared_ptr
7 | from libc.stdint cimport (uint8_t, uint16_t, uint32_t, uint64_t,
8 | int8_t, int16_t, int32_t, int64_t)
9 | from libc.stdlib cimport malloc, realloc, free, exit
10 | from libcpp.string cimport string
11 | from libcpp.vector cimport vector
12 | from libcpp.complex cimport complex as ccomplex
13 | from cython.operator cimport dereference as deref
14 |
15 | import numpy as np
16 | import sys
17 |
18 | cdef extern from "" namespace "std" nogil:
19 | cdef cppclass optional[T]:
20 | ctypedef T value_type
21 | optional() except +
22 | optional(optional &) except +
23 | bint operator ==(optional &, optional &)
24 | bint operator !=(optional &, optional &)
25 | bint operator <(optional &, optional &)
26 | bint operator >(optional &, optional &)
27 | bint operator <=(optional &, optional &)
28 | bint operator >=(optional &, optional &)
29 | bint has_value() const
30 | T& value()
31 | T& operator *()
32 | void swap(optional &)
33 | void reset()
34 |
35 | cdef extern from "rxs_parsing_core/ModularPicoScenesFrame.hxx":
36 | # ModularPicoScenesFrame.hxx
37 | cdef packed struct ieee80211_mac_frame_header_frame_control_field:
38 | uint16_t version
39 | uint16_t type
40 | uint16_t subtype
41 | uint16_t toDS
42 | uint16_t fromDS
43 | uint16_t moreFrags
44 | uint16_t retry
45 | uint16_t power_mgmt
46 | uint16_t more
47 | uint16_t protect
48 | uint16_t order
49 |
50 | # ModularPicoScenesFrame.hxx
51 | cdef packed struct ieee80211_mac_frame_header:
52 | ieee80211_mac_frame_header_frame_control_field fc
53 | # uint16_t dur
54 | uint8_t addr1[6]
55 | uint8_t addr2[6]
56 | uint8_t addr3[6]
57 | uint16_t frag
58 | uint16_t seq
59 |
60 | # RxSBasicSegment.hxx
61 | cdef packed struct RxSBasic:
62 | uint16_t deviceType
63 | uint64_t tstamp
64 | uint64_t systemTime
65 | int16_t centerFreq
66 | int16_t controlFreq
67 | uint16_t cbw
68 | uint8_t packetFormat
69 | uint16_t pkt_cbw
70 | uint16_t guardInterval
71 | uint8_t mcs
72 | uint8_t numSTS
73 | uint8_t numESS
74 | uint8_t numRx
75 | # uint8_t numUser
76 | # uint8_t userIndex
77 | int8_t noiseFloor
78 | int8_t rssi
79 | int8_t rssi_ctl0
80 | int8_t rssi_ctl1
81 | int8_t rssi_ctl2
82 | int8_t rssi_ctl3
83 | int8_t rssi_ctl4
84 | int8_t rssi_ctl5
85 | int8_t rssi_ctl6
86 | int8_t rssi_ctl7
87 |
88 | # RxSBasicSegment.hxx
89 | cdef cppclass RxSBasicSegment:
90 | const RxSBasic & getBasic() const
91 |
92 | # RXSExtraInfo.hxx
93 | cdef enum AtherosCFTuningPolicy: # uint8_t
94 | CFTuningByChansel
95 | CFTuningByFastCC
96 | CFTuningByHardwareReset
97 | CFTuningByDefault
98 |
99 | # RXSExtraInfo.hxx
100 | cdef struct ExtraInfo:
101 | # uint32_t featureCode
102 | bint hasLength
103 | bint hasVersion
104 | bint hasMacAddr_cur
105 | bint hasMacAddr_rom
106 | bint hasChansel
107 | bint hasBMode
108 | bint hasEVM
109 | bint hasTxChainMask
110 | bint hasRxChainMask
111 | bint hasTxpower
112 | bint hasCF
113 | bint hasTxTSF
114 | bint hasLastHWTxTSF
115 | bint hasChannelFlags
116 | bint hasTxNess
117 | bint hasTuningPolicy
118 | bint hasPLLRate
119 | bint hasPLLRefDiv
120 | bint hasPLLClkSel
121 | bint hasAGC
122 | bint hasAntennaSelection
123 | bint hasSamplingRate
124 | bint hasCFO
125 | bint hasSFO
126 | # bint hasPreciseTxTiming
127 | uint16_t length
128 | uint64_t version
129 | uint8_t macaddr_rom[6]
130 | uint8_t macaddr_cur[6]
131 | uint32_t chansel
132 | uint8_t bmode
133 | int8_t evm[20]
134 | uint8_t txChainMask
135 | uint8_t rxChainMask
136 | uint8_t txpower
137 | uint64_t cf
138 | uint32_t txTSF
139 | uint32_t lastHwTxTSF
140 | uint16_t channelFlags
141 | uint8_t tx_ness
142 | AtherosCFTuningPolicy tuningPolicy
143 | uint16_t pll_rate
144 | uint8_t pll_refdiv
145 | uint8_t pll_clock_select
146 | uint8_t agc
147 | uint8_t ant_sel[3]
148 | uint64_t samplingRate
149 | int32_t cfo
150 | int32_t sfo
151 | # double preciseTxTiming
152 |
153 | # ExtraInfoSegment.hxx
154 | cdef cppclass ExtraInfoSegment:
155 | const ExtraInfo & getExtraInfo() const
156 |
157 | # PicoScenesCommons.hxx
158 | cdef enum PicoScenesDeviceType: # uint16_t
159 | QCA9300
160 | IWL5300
161 | IWLMVM_AX200
162 | IWLMVM_AX210
163 | MAC80211Compatible
164 | USRP
165 | VirtualSDR
166 | Unknown
167 |
168 | # PicoScenesCommons.hxx
169 | cdef enum PacketFormatEnum: # int8_t
170 | PacketFormat_NonHT
171 | PacketFormat_HT
172 | PacketFormat_VHT
173 | PacketFormat_HESU
174 | PacketFormat_HEMU
175 | PacketFormat_Unknown
176 |
177 | # PicoScenesCommons.hxx
178 | cdef enum ChannelBandwidthEnum: # uint16_t
179 | CBW_5
180 | CBW_10
181 | CBW_20
182 | CBW_40
183 | CBW_80
184 | CBW_160
185 |
186 | # CSISegment.hxx
187 | cdef cppclass CSIDimension:
188 | uint16_t numTones
189 | uint8_t numTx
190 | uint8_t numRx
191 | uint8_t numESS
192 | uint16_t numCSI
193 |
194 | # SignalMatrix.hxx
195 | cdef cppclass SignalMatrix[T]:
196 | vector[T] array
197 | vector[int64_t] dimensions
198 |
199 | # CSISegment.hxx
200 | cdef cppclass CSI:
201 | PicoScenesDeviceType deviceType
202 | uint8_t firmwareVersion
203 | PacketFormatEnum packetFormat
204 | ChannelBandwidthEnum cbw
205 | uint64_t carrierFreq
206 | uint64_t samplingRate
207 | uint32_t subcarrierBandwidth
208 | CSIDimension dimensions
209 | uint8_t antSel
210 | # int16_t subcarrierOffset
211 | vector[int16_t] subcarrierIndices
212 | SignalMatrix[ccomplex[float]] CSIArray
213 | SignalMatrix[float] magnitudeArray
214 | SignalMatrix[float] phaseArray
215 |
216 | # CSISegment.hxx
217 | cdef cppclass CSISegment:
218 | const shared_ptr[CSI] & getCSI() const
219 |
220 | # DPASRequestSegment.hxx
221 | # cdef packed struct DPASRequest:
222 | # uint8_t requestMode
223 | # uint16_t batchId
224 | # uint16_t batchLength
225 | # uint16_t sequenceId
226 | # uint16_t intervalTime
227 | # uint16_t intervalStep
228 | # PicoScenesDeviceType deviceType
229 | # uint16_t deviceSubtype
230 | # uint64_t carrierFrequency
231 | # uint32_t samplingFrequency
232 |
233 | # cdef cppclass DPASRequestSegment:
234 | # DPASRequest getRequest() const
235 |
236 | # MVMExtraSegment.hxx
237 | cdef cppclass IntelMVMParsedCSIHeader:
238 | # uint32_t ftmClock
239 | # uint32_t muClock
240 | # uint32_t rate_n_flags
241 | uint32_t value56[9];
242 | uint32_t rateNflag;
243 | uint32_t value96[44];
244 |
245 | # MVMExtraSegment.hxx
246 | cdef cppclass IntelMVMExtrta:
247 | IntelMVMParsedCSIHeader parsedHeader
248 |
249 | # MVMExtraSegment.hxx
250 | cdef cppclass MVMExtraSegment:
251 | const IntelMVMExtrta & getMvmExtra() const
252 |
253 | # ModularPicoScenesFrame.hxx
254 | cdef packed struct PicoScenesFrameHeader:
255 | uint32_t magicValue
256 | uint32_t version
257 | PicoScenesDeviceType deviceType
258 | # uint8_t numSegments
259 | uint8_t frameType
260 | uint16_t taskId
261 | uint16_t txId
262 |
263 | # BasebandSignalSegment.hxx
264 | cdef cppclass BasebandSignalSegment:
265 | const SignalMatrix[ccomplex[float]] & getSignals() const
266 |
267 | # PreEQSymbolsSegment.hxx
268 | cdef cppclass PreEQSymbolsSegment:
269 | const SignalMatrix[ccomplex[float]] & getPreEqSymbols() const
270 | # ModularPicoScenesFrame.hxx
271 | cdef cppclass ModularPicoScenesRxFrame:
272 | ieee80211_mac_frame_header standardHeader
273 | shared_ptr[RxSBasicSegment] rxSBasicSegment
274 | shared_ptr[ExtraInfoSegment] rxExtraInfoSegment
275 | shared_ptr[CSISegment] csiSegment
276 | shared_ptr[MVMExtraSegment] mvmExtraSegment
277 | shared_ptr[PicoScenesFrameHeader] PicoScenesHeader
278 | shared_ptr[ExtraInfoSegment] txExtraInfoSegment
279 | # optional[DPASRequestSegment] dpasRequestSegment
280 | # optional[CSISegment] pilotCSISegment
281 | shared_ptr[CSISegment] legacyCSISegment
282 | shared_ptr[BasebandSignalSegment] basebandSignalSegment
283 | #optional[PreEQSymbolsSegment] preEQSymbolsSegment
284 | vector[vector[uint8_t]] mpdus
285 |
286 | @ staticmethod
287 | optional[ModularPicoScenesRxFrame] fromBuffer(const uint8_t *buffer, uint32_t bufferLength, bint interpolateCSI)
288 | string toString() const
289 |
290 |
291 | cdef class Picoscenes:
292 | cdef readonly str file
293 | cdef readonly int count
294 | cdef public list raw
295 | cdef bint if_report
296 |
297 | def __cinit__(self, file, if_report=True, *argv, **kw):
298 | self.file = file
299 | self.if_report = if_report
300 | self.raw = list()
301 | self.read()
302 |
303 | def __init__(self, file, if_report=True):
304 | pass
305 |
306 | cpdef read(self):
307 | self.seek(self.file, 0, 0)
308 |
309 | cpdef seek(self, file, long pos, long num):
310 | cdef FILE *f
311 |
312 | tempfile = file.encode(encoding="utf-8")
313 | cdef char *datafile = tempfile
314 |
315 | f = fopen(datafile, "rb")
316 | if f is NULL:
317 | printf("Open failed!\n")
318 | sys.exit(-1)
319 |
320 | fseek(f, 0, SEEK_END)
321 | cdef long lens = ftell(f)
322 | fseek(f, pos, SEEK_SET)
323 |
324 | cdef int count = 0
325 | cdef int l, i
326 | cdef uint32_t field_len
327 | cdef uint32_t buf_size = 1024
328 | cdef unsigned char *buf
329 | cdef optional[ModularPicoScenesRxFrame] frame
330 |
331 | if num == 0:
332 | num = lens
333 | if self.raw:
334 | self.raw.clear()
335 |
336 | buf = malloc(buf_size * sizeof(unsigned char)) # CHECK NULL
337 | while pos < (lens - 4):
338 | l = fread(buf, sizeof(unsigned char), 4, f)
339 | if l < 4:
340 | break
341 | field_len = cu32l(buf[0], buf[1], buf[2], buf[3]) + 4
342 | fseek(f, -4, SEEK_CUR)
343 | if buf_size < field_len:
344 | buf = realloc(buf, field_len) # CHECK NULL
345 | buf_size = field_len
346 | l = fread(buf, sizeof(unsigned char), field_len, f)
347 |
348 | # rxs_parsing_core
349 | frame = ModularPicoScenesRxFrame.fromBuffer(buf, field_len, True)
350 | self.raw.append(parse(&frame))
351 |
352 | pos += field_len
353 | count += 1
354 | if count >= num:
355 | break
356 | free(buf)
357 | fclose(f)
358 | self.count = count
359 |
360 | cpdef pmsg(self, unsigned char *data):
361 | # This method hasn't been ready
362 | frame = ModularPicoScenesRxFrame.fromBuffer(data, len(data), True)
363 | if self.raw:
364 | self.raw.pop(0)
365 | self.raw.append(parse(&frame))
366 | if self.if_report and self.raw[0]:
367 | print(frame.value().toString())
368 | self.count = 1
369 | return 0xf300 # status code
370 |
371 | cdef inline uint32_t cu32l(uint8_t a, uint8_t b, uint8_t c, uint8_t d):
372 | return a | (b << 8) | (c << 16) | (d << 24)
373 |
374 |
375 | cdef parse_ieee80211_mac_frame_header(const ieee80211_mac_frame_header *m):
376 | cdef int i
377 | return {
378 | "ControlField": {
379 | 'Version': m.fc.version,
380 | 'Type': m.fc.type,
381 | 'SubType': m.fc.subtype,
382 | 'ToDS': m.fc.toDS,
383 | 'FromDS': m.fc.fromDS,
384 | 'MoreFrags': m.fc.moreFrags,
385 | 'Retry': m.fc.retry,
386 | 'PowerManagement': m.fc.power_mgmt,
387 | 'More': m.fc.more,
388 | 'Protected': m.fc.protect,
389 | 'Order': m.fc.order,
390 | },
391 | "Addr1": [m.addr1[i] for i in range(6)],
392 | "Addr2": [m.addr2[i] for i in range(6)],
393 | "Addr3": [m.addr3[i] for i in range(6)],
394 | "Fragment": m.frag,
395 | "Sequence": m.seq,
396 | }
397 |
398 | cdef parse_RxSBasic(const RxSBasic *m):
399 | return {
400 | "deviceType": m.deviceType,
401 | "timestamp": m.tstamp,
402 | "systemns": m.systemTime,
403 | "centerFreq": m.centerFreq,
404 | "controlFreq": m.controlFreq,
405 | "CBW": m.cbw,
406 | "packetFormat": m.packetFormat,
407 | "packetCBW": m.pkt_cbw,
408 | "GI": m.guardInterval,
409 | "MCS": m.mcs,
410 | "numSTS": m.numSTS,
411 | "numESS": m.numESS,
412 | "numRx": m.numRx,
413 | "noiseFloor": m.noiseFloor,
414 | "rssi": m.rssi,
415 | "rssi1": m.rssi_ctl0,
416 | "rssi2": m.rssi_ctl1,
417 | "rssi3": m.rssi_ctl2,
418 | "rssi4": m.rssi_ctl3,
419 | "rssi5": m.rssi_ctl4,
420 | "rssi6": m.rssi_ctl5,
421 | "rssi7": m.rssi_ctl6,
422 | "rssi8": m.rssi_ctl7,
423 | }
424 |
425 | cdef parse_ExtraInfo(const ExtraInfo *m):
426 | cdef int i
427 | result = {
428 | "hasLength": m.hasLength,
429 | "hasVersion": m.hasVersion,
430 | "hasMacAddr_cur": m.hasMacAddr_cur,
431 | "hasMacAddr_rom": m.hasMacAddr_rom,
432 | "hasChansel": m.hasChansel,
433 | "hasBMode": m.hasBMode,
434 | "hasEVM": m.hasEVM,
435 | "hasTxChainMask": m.hasTxChainMask,
436 | "hasRxChainMask": m.hasRxChainMask,
437 | "hasTxpower": m.hasTxpower,
438 | "hasCF": m.hasCF,
439 | "hasTxTSF": m.hasTxTSF,
440 | "hasLastHwTxTSF": m.hasLastHWTxTSF,
441 | "hasChannelFlags": m.hasChannelFlags,
442 | "hasTxNess": m.hasTxNess,
443 | "hasTuningPolicy": m.hasTuningPolicy,
444 | "hasPLLRate": m.hasPLLRate,
445 | "hasPLLClkSel": m.hasPLLClkSel,
446 | "hasPLLRefDiv": m.hasPLLRefDiv,
447 | "hasAGC": m.hasAGC,
448 | "hasAntennaSelection": m.hasAntennaSelection,
449 | "hasSamplingRate": m.hasSamplingRate,
450 | "hasCFO": m.hasCFO,
451 | "hasSFO": m.hasSFO,
452 | }
453 | if m.hasLength:
454 | result["length"] = m.length
455 | if m.hasVersion:
456 | result["version"] = m.version
457 | if m.hasMacAddr_cur:
458 | result["macaddr_cur"] = [m.macaddr_cur[i] for i in range(6)]
459 | if m.hasMacAddr_rom:
460 | result["macaddr_rom"] = [m.macaddr_rom[i] for i in range(6)]
461 | if m.hasChansel:
462 | result["chansel"] = m.chansel
463 | if m.hasBMode:
464 | result["bmode"] = m.bmode
465 | if m.hasEVM:
466 | result["evm"] = [m.evm[i] for i in range(18)]
467 | if m.hasTxChainMask:
468 | result["tx_chainmask"] = m.txChainMask
469 | if m.hasRxChainMask:
470 | result["rx_chainmask"] = m.rxChainMask
471 | if m.hasTxpower:
472 | result["txpower"] = m.txpower
473 | if m.hasCF:
474 | result["cf"] = m.cf
475 | if m.hasTxTSF:
476 | result["txtsf"] = m.txTSF
477 | if m.hasLastHWTxTSF:
478 | result["last_txtsf"] = m.lastHwTxTSF
479 | if m.hasChannelFlags:
480 | result["channel_flags"] = m.channelFlags
481 | if m.hasTxNess:
482 | result["tx_ness"] = m.tx_ness
483 | if m.hasTuningPolicy:
484 | result["tuning_policy"] = m.tuningPolicy
485 | if m.hasPLLRate:
486 | result["pll_rate"] = m.pll_rate
487 | if m.hasPLLClkSel:
488 | result["pll_clock_select"] = m.pll_clock_select
489 | if m.hasPLLRefDiv:
490 | result["pll_refdiv"] = m.pll_refdiv
491 | if m.hasAGC:
492 | result["agc"] = m.agc
493 | if m.hasAntennaSelection:
494 | result["ant_sel"] = [m.ant_sel[i] for i in range(3)]
495 | if m.hasSamplingRate:
496 | result["sf"] = m.samplingRate
497 | if m.hasCFO:
498 | result["cfo"] = m.cfo
499 | if m.hasSFO:
500 | result["sfo"] = m.sfo
501 | return result
502 |
503 | cdef parse_CSI(const CSI *m):
504 | return {
505 | "DeviceType": m.deviceType,
506 | "PacketFormat": m.packetFormat,
507 | "FirmwareVersion": m.firmwareVersion,
508 | "CBW": m.cbw,
509 | "CarrierFreq": m.carrierFreq,
510 | "SamplingRate": m.samplingRate,
511 | "SubcarrierBandwidth": m.subcarrierBandwidth,
512 | "numTones": m.dimensions.numTones,
513 | "numTx": m.dimensions.numTx,
514 | "numRx": m.dimensions.numRx,
515 | "numESS": m.dimensions.numESS,
516 | "numCSI": m.dimensions.numCSI,
517 | "ant_sel": m.dimensions.numESS,
518 | "CSI": m.CSIArray.array,
519 | "Mag": m.magnitudeArray.array,
520 | "Phase": m.phaseArray.array,
521 | "SubcarrierIndex": m.subcarrierIndices,
522 | }
523 |
524 | cdef parse_IntelMVMParsedCSIHeader(const IntelMVMParsedCSIHeader *m):
525 | result = {
526 | "value56":[m.value56[i] for i in range(9)],
527 | "rateNflag": m.rateNflag,
528 | "value96": [m.value96[i] for i in range(44)] ,
529 | }
530 | return result
531 |
532 | # cdef parse_DpasRequestSegment(const DPASRequest m):
533 | # return {
534 | # "requestMode": m.requestMode,
535 | # "batchId": m.batchId,
536 | # "batchLength": m.batchLength,
537 | # "sequenceId": m.sequenceId,
538 | # "intervalTime": m.intervalTime,
539 | # "intervalStep": m.intervalStep,
540 | # "deviceType": m.deviceType,
541 | # "deviceSubtype": m.deviceSubtype,
542 | # "carrierFrequency": m.carrierFrequency,
543 | # "samplingFrequency": m.samplingFrequency,
544 | # }
545 |
546 | cdef parse_PicoScenesFrameHeader(const PicoScenesFrameHeader *m):
547 | return {
548 | "MagicValue": m.magicValue,
549 | "Version": m.version,
550 | "DeviceType": m.deviceType,
551 | "FrameType": m.frameType,
552 | "TaskId": m.taskId,
553 | "TxId": m.txId,
554 | }
555 |
556 | cdef parse_SignalMatrix(const SignalMatrix[ccomplex[float]] *m):
557 | return np.asarray(m.array).reshape(m.dimensions)
558 |
559 | cdef parse(optional[ModularPicoScenesRxFrame] *frame):
560 | cdef const SignalMatrix[ccomplex[float]] *sig_matrix_ptr = NULL # 在这里声明
561 | data = {}
562 | cdef ModularPicoScenesRxFrame frame_value
563 | if frame.has_value():
564 | frame_value = frame.value()
565 | data = {
566 | "StandardHeader": parse_ieee80211_mac_frame_header(&frame_value.standardHeader),
567 | "RxSBasic": parse_RxSBasic(&(deref(frame_value.rxSBasicSegment).getBasic())),
568 | "RxExtraInfo": parse_ExtraInfo(&(deref(frame_value.rxExtraInfoSegment).getExtraInfo())),
569 | "CSI": parse_CSI(&(deref(deref(frame_value.csiSegment).getCSI()))),
570 | }
571 | if frame_value.mvmExtraSegment:
572 | data["MVMExtra"] = parse_IntelMVMParsedCSIHeader(
573 | &(deref(frame_value.mvmExtraSegment).getMvmExtra().parsedHeader))
574 | if frame_value.PicoScenesHeader:
575 | data["PicoScenesHeader"] = parse_PicoScenesFrameHeader(&(deref(frame_value.PicoScenesHeader)))
576 | if frame_value.txExtraInfoSegment:
577 | data["TxExtraInfo"] = parse_ExtraInfo(&(deref(frame_value.txExtraInfoSegment).getExtraInfo()))
578 | # if frame_value.pilotCSISegment:
579 | # data["PilotCSI"] = parse_CSI(&(deref(deref(frame_value.pilotCSISegment).getCSI())))
580 | if frame_value.legacyCSISegment:
581 | data["LegacyCSI"] = parse_CSI(&(deref(deref(frame_value.legacyCSISegment).getCSI())))
582 | # if frame_value.basebandSignalSegment:
583 | # #data["BasebandSignals"] = parse_SignalMatrix(&(deref(frame_value.basebandSignalSegment).getFloat32SignalMatrix()))
584 | # data["BasebandSignals"] = parse_SignalMatrix(&(deref(frame_value.basebandSignalSegment).getSignals()))
585 |
586 | if frame_value.basebandSignalSegment:
587 | sig_matrix_ptr = &deref(frame_value.basebandSignalSegment).getSignals()
588 | data["BasebandSignals"] = parse_SignalMatrix(sig_matrix_ptr)
589 |
590 | # if frame_value.preEQSymbolsSegment:
591 | # data["PreEQSymbols"] = parse_SignalMatrix(&(deref(frame_value.preEQSymbolsSegment).getPreEqSymbols()))
592 | # if frame_value.dpasRequestSegment:
593 | # data["DpasRequestSegment"] = parse_DpasRequestSegment(
594 | # deref(frame_value.dpasRequestSegment).getRequest())
595 |
596 | data["MPDUS"] = frame_value.mpdus
597 |
598 | # print(data)
599 | return data
600 |
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [build-system]
2 | requires = ["setuptools>=68", "wheel", "Cython>=3.0.10", "numpy>=1.26"]
3 | build-backend = "setuptools.build_meta"
4 |
5 | [project]
6 | name = "picoscenes"
7 | version = "0.1.0"
8 | description = "Cython bindings for the PicoScenes Wi-Fi measurement parser"
9 | readme = "README.md"
10 | requires-python = ">=3.8"
11 | authors = [{name = "Tian Teng"}]
12 | dependencies = ["numpy>=1.21.3"]
13 |
14 | [project.optional-dependencies]
15 | demo = [
16 | "matplotlib"
17 | ]
18 |
--------------------------------------------------------------------------------
/rx_by_usrpN210.csi:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wifisensing/PicoScenes-Python-Toolbox/f676855accedf79e848bf2f1d10ee5c1f649a33a/rx_by_usrpN210.csi
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 | # -*- coding: utf-8 -*-
3 | import os
4 | from pathlib import Path
5 | from typing import List
6 |
7 | import numpy
8 | from Cython.Build.Dependencies import cythonize
9 | from setuptools import find_packages, setup
10 | from setuptools.command.build_ext import build_ext
11 | from setuptools.extension import Extension
12 |
13 |
14 | def find_files(root: Path, ext: str) -> List[str]:
15 | """
16 | Search for files of given extension recursively within root
17 | """
18 | return [str(file) for file in Path(root).glob("*" + ext)]
19 |
20 |
21 | class Build(build_ext):
22 | def build_extensions(self):
23 | if self.compiler.compiler_type in ["unix", "mingw32"]:
24 | for e in self.extensions:
25 | if e.name == "picoscenes":
26 | e.extra_compile_args = ["-std=c++2a", "-Wno-attributes", "-O3"]
27 | if self.compiler.compiler_type in ["msvc"]:
28 | for e in self.extensions:
29 | if e.name == "picoscenes":
30 | e.extra_compile_args = ["/std:c++latest"]
31 | super(Build, self).build_extensions()
32 |
33 |
34 | pico_root = Path.cwd() / "rxs_parsing_core"
35 | pico_generated = pico_root / "preprocess" / "generated"
36 | pico_include = pico_root / "preprocess"
37 | pico_source = find_files(pico_root, ".cxx") + find_files(pico_generated, ".cpp")
38 | pico_extension = Extension(
39 | "picoscenes",
40 | ["./picoscenes.pyx"] + pico_source,
41 | include_dirs=[numpy.get_include(), str(pico_include)],
42 | define_macros=[("NPY_NO_DEPRECATED_API", "NPY_1_7_API_VERSION")],
43 | )
44 |
45 |
46 | if not pico_root.is_dir():
47 | raise RuntimeError(
48 | f"Parsing core submodule {pico_root} is not a directory; "
49 | + "Did you initialize the submodule?"
50 | )
51 |
52 | EXTENSIONS = [pico_extension]
53 |
54 |
55 | setup(
56 | name="picoscenes",
57 | version="0.1.0",
58 | packages=find_packages(),
59 | install_requires=["numpy"],
60 | python_requires=">=3",
61 | ext_modules=cythonize(
62 | EXTENSIONS, compiler_directives={"language_level": 3, "binding": False}
63 | ),
64 | cmdclass={"build_ext": Build},
65 | )
66 |
--------------------------------------------------------------------------------
/test.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | import numpy as np
4 |
5 | from picoscenes import Picoscenes
6 |
7 |
8 | class TestConsistency(unittest.TestCase):
9 | def setUp(self):
10 | self.frames = Picoscenes("rx_by_usrpN210.csi")
11 | self.frame = self.frames.raw[0] # Get the first frame in the list
12 | self.subcarrier_indices = np.array(self.frame.get("CSI").get("SubcarrierIndex"))
13 | self.n_tones = self.frame.get("CSI").get("numTones")
14 | self.n_rx_antennas = self.frame.get("RxSBasic").get("numRx")
15 | self.n_streams = self.frame.get("RxSBasic").get("numSTS")
16 |
17 | def test_number_of_frames(self):
18 | # Test file contains 74 frames
19 | n_frames = 74
20 | self.assertEqual(len(self.frames.raw), n_frames)
21 |
22 | def test_subcarrier_indices(self):
23 | self.assertTrue(np.array_equal(self.subcarrier_indices, np.arange(-26, 27)))
24 |
25 | def test_magnitude(self):
26 | # Check shape of magnitude is correctly read
27 | mag = np.array(self.frame.get("CSI").get("Mag"))
28 | self.assertEqual(len(mag), self.n_tones)
29 |
30 | def test_csi(self):
31 | csi = np.array(self.frame.get("CSI").get("CSI"))
32 | self.assertEqual(len(csi), self.n_tones * self.n_streams * self.n_rx_antennas)
33 |
34 | # Just test whether this doesn't crash
35 | csi = csi.reshape((self.n_tones, self.n_streams, self.n_rx_antennas), order="F")
36 |
37 |
38 | if __name__ == "__main__":
39 | unittest.main()
40 |
--------------------------------------------------------------------------------