121 lines
4.6 KiB
Python
121 lines
4.6 KiB
Python
import struct
|
|
|
|
import numpy as np
|
|
import numpy.typing as npt
|
|
|
|
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) -> None:
|
|
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) -> npt.NDArray[np.complex64]:
|
|
csi_matrix: npt.NDArray[np.complex64] = np.zeros(
|
|
(
|
|
header.num_subcarriers,
|
|
header.num_rx,
|
|
header.num_tx,
|
|
),
|
|
dtype=np.complex64,
|
|
)
|
|
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) -> None:
|
|
self.header = CSIHeader(data[:272])
|
|
self.matrix = self.parseCsiData(
|
|
data[272 : 272 + self.header.csi_size], self.header
|
|
)
|