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

项目地址:
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 np
import 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

  • 12
    点赞
  • 100
    收藏
    觉得还不错? 一键收藏
  • 26
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 26
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值