将二进制时间戳转换为字符串

2 投票
2 回答
1941 浏览
提问于 2025-04-18 06:55

我正在尝试用Python解析一种专有的二进制格式(Wintec NAL)。现在已经有一段能正常工作的C语言代码(作者:Dennis Heynlein),我想把它移植到Python上。

不过,我在理解这段C代码的某些部分时遇到了困难。以下是C语言中对这种二进制格式的定义:

/*
 * File extension:. NAL
 * File format: binary, 32 byte fixed block length
 */

/*
 * For now we will read raw structs direct from the data file, ignoring byte
 * order issues (since the data is in little-endian form compatible with i386)
 *
 * XXX TODO:  write marshalling functions to read records in the proper
 * byte-order agnostic way.
 */
#pragma pack (1)

typedef struct nal_data32 {
  unsigned char point_type; /* 0 - normal, 1 - start, 2 - marked */

  unsigned char padding_1;

  unsigned int second: 6, minute: 6, hour: 5;
  unsigned int day: 5, month: 4, year: 6; /* add 2000 to year */

  signed int latitude;    /* divide by 1E7 for degrees */
  signed int longitude;   /* divide by 1E7 for degrees */

  unsigned short height;    /* meters */

  signed char temperature;  /* °C */

  unsigned short pressure;  /* mbar */

  unsigned char cadence;    /* RPM */
  unsigned char pulse;    /* BPM */

  signed char slope;    /* degrees */

  signed short compass;   /* °Z axis */
  signed short roll;    /* °X axis */
  signed short yaw;   /* °Y axis */

  unsigned char speed;    /* km/h */

  unsigned char bike;   /* ID# 0-3 */

  unsigned char padding_2;
  unsigned char padding_3;
} nal_t;

我正在使用python-bitstring库来在Python中实现相同的功能,但我对上面给出的时间格式感到困惑,不知道该如何将其转换为Python。

from bitstring import ConstBitStream
nal_format=('''
    uint:8,
    uint:8,
    bin:32,
    intle:32,
    intle:32,
    uint:16,
    uint:8,
    uint:16,
    uint:8,
    uint:8,
    uint:8,
    uint:16,
    uint:16,
    uint:16,
    uint:8,
    uint:8,
    uint:8,
    uint:8
''')

f = ConstBitStream('0x01009f5a06379ae1cb13f7a6b62bca010dc703000000c300fefff9ff00000000')
f.pos=0

#type,padding1,second,minute,hour,day,month,year,lat,lon,height,temp,press,cad,pulse,slope,compass,roll,yaw,speed,bike,padding2,padding3=f.peeklist(nal_format)

type,padding1,time,lat,lon,height,temp,press,cad,pulse,slope,compass,roll,yaw,speed,bike,padding2,padding3=f.readlist(nal_format)

print type
print padding1
#print second 
#print minute
#print hour
#print day
#print month
#print year
print time
print lat
print lon

虽然我已经弄明白了纬度和经度需要用小端格式来定义,但我不知道如何调整32位的时间戳,以使其符合C语言定义中的格式(而且我也没能找到与“高度”对应的掩码,因此也没尝试后面的字段)。

以上十六进制字符串的值如下:

  • 日期:2013/12/03-T05:42:31
  • 位置:东经73.3390583°,北纬33.2128666°
  • 方向:195°,滚转 -2°,偏航 -7°
  • 高度:458米
  • 温度:13 °C
  • 气压:967 mb

2 个回答

3

在'C'结构中,时间戳是一个'C'位域。编译器会根据冒号后面的数字来分配在这个更大字段定义中的位数。在这个例子里,它是一个无符号整型(4个字节)。想要更详细的解释,可以看看这里。位域的一个大问题是,位的分配是根据计算机的字节序来决定的,所以它们在不同的系统间不太兼容。

你的Python格式声明似乎有个错误。可能需要额外分配一个4字节的无符号整型来表示日期。像这样:

nal_format=('''
    uint:8,
    uint:8,
    bin:32,
    bin:32,
    intle:32,
    intle:32,
''')

要在Python中表示位域,可以使用Python的位数组来表示这些位。可以查看这个链接

还有一点需要注意的是,结构中的pack(1)。它告诉编译器在一个字节的边界上对齐。换句话说,就是在字段之间不要添加任何填充。通常情况下,对齐是4个字节,这样编译器会让每个字段从4字节的边界开始。想了解更多信息,可以查看这里

4

我对 bitstring 这个东西不太熟悉,所以我会把你的输入转换成打包的二进制数据,然后用 struct 来处理它。如果你对这部分不感兴趣,可以直接跳到后面。

import binascii

packed = binascii.unhexlify('01009f5a06379ae1cb13f7a6b62bca010dc703000000c300fefff9ff00000000')

如果你想的话,我可以更详细地讲讲这部分。其实就是把 '0100...' 转换成 b'\x01\x00...'

现在,解包的时候唯一需要注意的是,你只想解包一个无符号整数,因为那个比特字段正好适合32位(一个无符号整数的宽度):

format = '<ccIiiHbHBBbhhhBBBB'

import struct

struct.unpack(format,packed)
Out[49]: 
('\x01',
 '\x00',
923163295,
...
)

这样就把输出转换成我们可以使用的格式。你可以像之前那样,把它解包成一长串变量。


现在,你的问题似乎是关于如何对 time(上面提到的 923163295)进行掩码处理,以从比特字段中获取正确的值。这只是一些简单的数学运算:

second_mask = 2**6 - 1
minute_mask = second_mask << 6
hour_mask = (2**5 - 1) << (6+6)
day_mask = hour_mask << 5
month_mask = (2**4 - 1) << (6+6+5+5)
year_mask = (2**6 - 1) << (6+6+5+5+4)

time & second_mask
Out[59]: 31

(time & minute_mask) >> 6
Out[63]: 42

(time & hour_mask) >> (6+6)
Out[64]: 5

(time & day_mask) >> (6+6+5)
Out[65]: 3

(time & month_mask) >> (6+6+5+5)
Out[66]: 12

(time & year_mask) >> (6+6+5+5+4)
Out[67]: 13L

如果用函数的形式来看,这一切就显得更自然了:

def unmask(num, width, offset):
     return (num & (2**width - 1) << offset) >> offset

想想看,这可以重新排列成:

def unmask(num, width, offset):
     return (num >> offset) & (2**width - 1)

unmask(time, 6, 0)
Out[77]: 31

unmask(time, 6, 6)
Out[78]: 42

#etc

如果你想要更复杂一点,

from itertools import starmap
from functools import partial

width_offsets = [(6,0),(6,6),(5,12),(5,17),(4,22),(6,26)]

list(starmap(partial(unmask,time), width_offsets))
Out[166]: [31, 42, 5, 3, 12, 13L]

把所有这些数字格式化正确,最后就能得到预期的日期/时间:

'20{:02d}/{:02d}/{:02d}-T{:02d}:{:02d}:{:02d}'.format(*reversed(_))
Out[167]: '2013/12/03-T05:42:31'

(可能有一种方法可以用 bitstring 模块优雅地完成所有这些位运算,但我觉得从基本原理出发解决问题更让人满意。)

撰写回答