您的位置:首页 > 编程语言 > Python开发

用python读取dat文件,wifi数据采集工具csi tool数据文件(.dat文件)解析

2020-02-03 06:30 4203 查看

项目地址:
https://github.com/hongshixian/CSI_reader

前言

数据采集工具csi_tool采集数据并保存为后缀.dat的数据文件,在csi_tool中提供一个c语言函数解析此文件。阅读了c语言的解析代码后发现,数据文件的组织方法与计网中数据十分相似,但略有不同。

参考代码

一部分c语言代码:
unsigned int timestamp_low = inBytes[0] + (inBytes[1] << 8) + (inBytes[2] << 16) + (inBytes[3] << 24);
unsigned short bfee_count = inBytes[4] + (inBytes[5] << 8);
unsigned int Nrx = inBytes[8];
unsigned int Ntx = inBytes[9];
unsigned int rssi_a = inBytes[10];
unsigned int rssi_b = inBytes[11];
unsigned int rssi_c = inBytes[12];
char noise = inBytes[13];
unsigned int agc = inBytes[14];
unsigned int antenna_sel = inBytes[15];
unsigned int len = inBytes[16] + (inBytes[17] << 8);
unsigned int fake_rate_n_flags = inBytes[18] + (inBytes[19] << 8);

数据格式

总体上,整个文件仅由n个bfee组成,巧了,数据文件中应当包含有n个采样信息,这个bfee的意义不言而喻,就是和采样一一对应。

bfee:

bfee的数据结构如上图所示。
前两字节是field_len,之后一字节是code,再之后便是可变长度的field。field_len等于code+field的字长。
当code为187时,表示field中是信道信息;不是187时,表示field中是其他信息。
我们关心的是信道信息,其他信息不解析,跳过该bfee即可。

field:

若code等于187,field有如上图数据格式。
到这里你一定感觉很熟悉了。
field分为头部和有效载荷(payload)两部分。头部有20字节的固定长度,有效载荷是个可变长度,字长为len。
头部各字段的数据类型和意义如下表:

可以见得,头部中包含了主要的信道信息。
而其中最重要的csi矩阵,分为30个subc,保存在有效载荷中。
分别对应30个子载波。

subc的结构如下表所示:

复数的结构:

每个subc的开始会有3位的非数据部分,因此subc的长度不是字节(8位)的整数倍,这将导致subc这部分的解析需要按比特操作,增加我解析工作的复杂度。

到这里,整个文件的数据结构都清楚了,开始试着用python来解析run-lxx.dat这个文件。
(真想交给王福超来写啊zzz)

文件解析

我依旧会使用面向对象的方式构建类,不过构造方法无力,属性太多,我选择用静态方法添加属性的方式构建对象。

import numpy as np
class Bfee:

def __init__(self):
pass

@staticmethod
def from_file(filename, model_name_encode="shift-JIS"):

with open(filename, "rb") as f:
from functools import reduce
array = bytes(reduce(lambda x, y: x+y, list(f))) # reduce(函数,list),将list中元素依次累加

bfee = Bfee()

#         vmd.current_index = 0
bfee.file_len = len(array)
bfee.dicts = []
bfee.all_csi = []

#         vmd.timestamp_low0 = int.from_bytes(array[3:7], byteorder='little', signed=False)

#         array = array[3:]

#%% Initialize variables
#ret = cell(ceil(len/95),1);    # % Holds the return values - 1x1 CSI is 95 bytes big, so this should be upper bound
cur = 0                       # % Current offset into file
count = 0                    # % Number of records output
broken_perm = 0              # % Flag marking whether we've encountered a broken CSI yet
triangle = [0, 1, 3]           # % What perm should sum to for 1,2,3 antennas

while cur < (bfee.file_len - 3):
#% Read size and code
#% 将文件数据读取到维度为 sizeA 的数组 A 中,并将文件指针定位到最后读取的值之后。fread 按列顺序填充 A。
bfee.field_len = int.from_bytes(array[cur:cur+2], byteorder='big', signed=False)
bfee.code = array[cur+2]
cur = cur+3

# there is CSI in field if code == 187,If unhandled code skip (seek over) the record and continue
if bfee.code == 187:
pass
else:
#% skip all other info
cur = cur + bfee.field_len - 1
continue

# get beamforming or phy data
if bfee.code == 187:
count =count + 1

bfee.timestamp_low = int.from_bytes(array[cur:cur+4], byteorder='little', signed=False)
bfee.bfee_count = int.from_bytes(array[cur+4:cur+6], byteorder='little', signed=False)
bfee.Nrx = array[cur+8]
bfee.Ntx = array[cur+9]
bfee.rssi_a = array[cur+10]
bfee.rssi_b = array[cur+11]
bfee.rssi_c = array[cur+12]
bfee.noise = array[cur+13] - 256
bfee.agc = array[cur+14]
bfee.antenna_sel = array[cur+15]
bfee.len = int.from_bytes(array[cur+16:cur+18], byteorder='little', signed=False)
bfee.fake_rate_n_flags = int.from_bytes(array[cur+18:cur+20], byteorder='little', signed=False)
bfee.calc_len = (30 * (bfee.Nrx * bfee.Ntx * 8 * 2 + 3) + 6) / 8
bfee.csi = np.zeros(shape=(30,bfee.Nrx ,bfee.Ntx), dtype=np.dtype(np.complex))
bfee.perm = [1,2,3]
bfee.perm[0] = ((bfee.antenna_sel) & 0x3)
bfee.perm[1] = ((bfee.antenna_sel >> 2) & 0x3)
bfee.perm[2] = ((bfee.antenna_sel >> 4) & 0x3)

cur = cur + 20

#get payload
payload = array[cur:cur+bfee.len]
cur = cur + bfee.len

index = 0

#Check that length matches what it should
if (bfee.len != bfee.calc_len):
print("MIMOToolbox:read_bfee_new:size","Wrong beamforming matrix size.")

#Compute CSI from all this crap :
#import struct
for i in range(30):
index += 3
remainder = index % 8
for j in range(bfee.Nrx):
for k in range(bfee.Ntx):
real_bin = bytes([(payload[int(index / 8)] >> remainder) | (payload[int(index/8+1)] << (8-remainder)) & 0b11111111])
real = int.from_bytes(real_bin, byteorder='little', signed=True)
imag_bin = bytes([(payload[int(index / 8+1)] >> remainder) | (payload[int(index/8+2)] << (8-remainder)) & 0b11111111])
imag = int.from_bytes(imag_bin, byteorder='little', signed=True)
tmp = np.complex(float(real), float(imag))
bfee.csi[i, j, k] = tmp
index += 16

# % matrix does not contain default values
if sum(bfee.perm) != triangle[bfee.Nrx-1]:
print('WARN ONCE: Found CSI (',filename,') with Nrx=', bfee.Nrx,' and invalid perm=[',bfee.perm,']\n' )
else:
temp_csi = np.zeros(bfee.csi.shape, dtype=np.dtype(np.complex))
# bfee.csi[:,bfee.perm[0:bfee.Nrx],:] = bfee.csi[:,0:bfee.Nrx,:]
for r in range(bfee.Nrx):
temp_csi[:,bfee.perm[r],:] = bfee.csi[:,r,:]
bfee.csi = temp_csi

# print phy data
#                 print(vmd.file_len,
#                       vmd.field_len,
#                       vmd.code,
#                       vmd.timestamp_low,
#                       vmd.bfee_count,
#                       vmd.Nrx,
#                       vmd.Ntx,
#                       vmd.rssi_a,
#                       vmd.rssi_b,
#                       vmd.rssi_c,
#                       vmd.noise,
#                       vmd.agc,
#                       vmd.antenna_sel,
#                       vmd.len,
#                       vmd.fake_rate_n_flags,
#                       vmd.calc_len,
#                       vmd.perm,
#                       vmd.csi.shape
#                      )

# 将类属性导出为dict,并返回
bfee_dict = {}
bfee_dict['timestamp_low'] = bfee.timestamp_low
bfee_dict['bfee_count'] = bfee.bfee_count
bfee_dict['Nrx'] = bfee.Nrx
bfee_dict['Ntx'] = bfee.Ntx
bfee_dict['rssi_a'] = bfee.rssi_a
bfee_dict['rssi_b'] = bfee.rssi_b
bfee_dict['rssi_c'] = bfee.rssi_c
bfee_dict['noise'] = bfee.noise
bfee_dict['agc'] = bfee.agc
bfee_dict['antenna_sel'] = bfee.antenna_sel
bfee_dict['perm'] = bfee.perm
bfee_dict['len'] = bfee.len
bfee_dict['fake_rate_n_flags'] = bfee.fake_rate_n_flags
bfee_dict['calc_len'] = bfee.calc_len
bfee_dict['csi'] = bfee.csi

bfee.dicts.append(bfee_dict)
bfee.all_csi.append(bfee.csi)

return bfee
if __name__ == '__main__':
bfee = Bfee.from_file("run-lxx.dat", model_name_encode="gb2312")
from pprint import pprint
pprint(len(bfee.dicts))
pprint(len(bfee.all_csi))
4993
4993

其他和总结

方法的返回两种结果:
bfee.dicts字段等同于read_bfee_file() 函数的返回的结果,适用于原来的处理步骤。
bfee.all_csi字段是所有csi矩阵的列表,可以直接转化成numpy数组,用来弥补字典性能低下的问题。
两个长度一样。

temp = np.array(vmd.all_csi)
np.savez('run-lxx.npz', temp)
temp.shape
(4993, 30, 3, 2)

保存为npz格式,
run-lxx.dat大小1.9Mb,run-lxx.npz变成了14.4Mb
两种文件的数据是一样多的,dat文件中复数的实部虚部用8位的sign int表示,npz文件中用64位的double表示,数据长度是原来的8倍,文件大小也变8倍。
可见.dat文件占用比较小

正确的matlab解析步骤应该是:
1.从文件将头部信息和csi矩阵读取到字典,即read_bfee_file()
2.依次从字典中取出标准化CSI,即get_scale_csi()
3.将所有csi整合到一起,保存为csv

标准化get_scale_csi()这个函数并不复杂,python实现之后,
python便可读取并处理dat文件。

标准化get_scale_csi()

import numpy as npimport math
def db(X, U):
R = 1
if 'power'.startswith(U):
assert X >= 0
else:
X = math.pow(abs(X), 2) / R

return (10 * math.log10(X) + 300) - 300
def dbinv(x):
return math.pow(10, x / 10)
def get_total_rss(csi_st):
# Careful here: rssis could be zero
rssi_mag = 0
if csi_st['rssi_a'] != 0:
rssi_mag = rssi_mag + dbinv(csi_st['rssi_a'])
if csi_st['rssi_b'] != 0:
rssi_mag = rssi_mag + dbinv(csi_st['rssi_b'])
if csi_st['rssi_c'] != 0:
rssi_mag = rssi_mag + dbinv(csi_st['rssi_c'])
return db(rssi_mag, 'power') - 44 - csi_st['agc']
def get_scale_csi(csi_st):
#Pull out csi
csi = csi_st['csi']
# print(csi.shape)
# print(csi)
#Calculate the scale factor between normalized CSI and RSSI (mW)
csi_sq = np.multiply(csi, np.conj(csi)).real
csi_pwr = np.sum(csi_sq, axis=0)
csi_pwr = csi_pwr.reshape(1, csi_pwr.shape[0], -1)
rssi_pwr = dbinv(get_total_rss(csi_st))

scale = rssi_pwr / (csi_pwr / 30)

if csi_st['noise'] == -127:
noise_db = -92
else:
noise_db = csi_st['noise']
thermal_noise_pwr = dbinv(noise_db)

quant_error_pwr = scale * (csi_st['Nrx'] * csi_st['Ntx'])

total_noise_pwr = thermal_noise_pwr + quant_error_pwr
ret = csi * np.sqrt(scale / total_noise_pwr)
if csi_st['Ntx'] == 2:
ret = ret * math.sqrt(2)
elif csi_st['Ntx'] == 3:
ret = ret * math.sqrt(dbinv(4.5))
return ret

get_scale_csi()是文件解析中常用的函数,随csi tool一起以matlab代码提供给使用者
在python中使用这个函数,要感谢 csi交流群3 中Siney同学的帮助,此函数的python代码这部分是由ta完成的
你可以根据需要,灵活的使用get_scale_csi()在你的python脚本中

类似这样:

if __name__ == '__main__':
bfee = Bfee.from_file("csi.dat", model_name_encode="gb2312")
for i in range(len(bfee.all_csi)):
csi = get_scale_csi(bfee.dicts[i])
print(csi[:,:,i])
[[-4.61880108-10.39230244j 14.43375551 +5.19615198j
5.1961519  +1.73205063j]
[12.12435284+23.67135555j  2.30940088 +8.08290308j
-5.77350211+17.32050633j]]
[[ -9.23760101+10.96965119j -14.43375542 -0.57735022j
0.         +4.61880157j]
[ 19.62990214-23.67135258j  -5.19615195 -7.50555282j
-19.62990667 +1.15470039j]]
[[-11.54700066 +8.66025049j  -6.92820252+12.70170462j
-2.30940073 -1.15470037j]
[ 29.44485168-15.58845089j -10.96965399 +0.57735021j
8.08290256-20.20725641j]]
[[  4.61880029+13.85640088j   8.66025261+15.5884547j
0.         +1.73205055j]
[  1.15470007-32.90895209j  -8.08290244+12.12435366j
-17.89785573+12.12435388j]]
[[ 16.16579928 +4.61879979j -16.74315425 -4.61880117j
1.15470031 +0.57735015j]
[-24.82604889-20.2072491j   -4.04145103-15.01110381j
22.51665597 +2.30940061j]]
[[-12.12435077-13.27905085j   4.61880156+12.7017043j
-2.8867509  -1.15470036j]
[  1.15470007+31.17690199j  -9.81495332+11.54700391j
-18.47520576+12.12435378j]]
[[ 15.01109927+13.27904936j   5.77350143+12.70170315j
4.04145118 +2.88675084j]
[ -6.92819966-29.44484858j -10.96965272+15.58845387j
16.16580471-12.7017037j ]]
[[-13.85640139+16.74315168j   9.81495338 -1.7320506j
5.19615164 +5.77350182j]
[ 26.55810266 -1.15470012j  12.12435417+12.12435417j
18.47520582 -9.23760291j]]
[[-24.82604699+12.70169846j  -3.46410083+10.39230249j
9.81495259 -1.73205046j]
[ 28.8674965  +8.66024895j -21.93930526 +6.92820166j
-0.57735015-21.93930579j]]
[[ -8.08289687-23.09399106j   4.61880085 -6.35085117j
-10.39230116 +0.j        ]
[-12.12434531+21.93929151j  18.4752034  -1.73205032j
-4.04145045+19.6299022j ]]

文中所有代码及数据文件可以在这里获取:
https://github.com/hongshixian/CSI_reader

  • 点赞
  • 收藏
  • 分享
  • 文章举报
YuanDianO 发布了1 篇原创文章 · 获赞 0 · 访问量 858 私信 关注
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: