Преобразование двоичной метки времени в строку

Я пытаюсь проанализировать проприетарный двоичный формат (Wintec NAL) с Python. Существует существующий и работающий C-код, который делает то же самое (Автор: Деннис Хейнлайн), который я пытаюсь перенести на 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

Хотя я понял, что широту и долготу нужно определять как little-endian, я понятия не имею, как адаптировать временную метку шириной 32 бита, чтобы она соответствовала формату, указанному в определении C (и я также не мог выяснить, соответствующая маска для "высоты" - соответственно я не пробовал поля после нее).

Это значения для шестнадцатеричной строки выше:

  • Дата: 2013/12/03-T05:42:31
  • положение: 73,3390583° E, 33,2128666° N
  • компас: 195°, крен -2°, рыскание -7°
  • альт: 458 метров
  • температура: 13 °C
  • прес: 967 мб

2 ответа

Решение

Я не знаком с bitstring, поэтому я преобразую ваши входные данные в упакованные двоичные данные, а затем использую struct справиться с этим. Пропустите до перерыва, если вы не заинтересованы в этой части.

import binascii

packed = binascii.unhexlify('01009f5a06379ae1cb13f7a6b62bca010dc703000000c300fefff9ff00000000')

Я могу перейти к этой части более подробно, если хотите. Это просто превращается '0100...' в b'\x01\x00...',

Теперь, единственная "ошибка" в распаковке, это выяснить, что вы хотите распаковать только ОДИН неподписанный тип int, так как это битовое поле помещается в 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 модуль, но я просто нахожу удовлетворение, чтобы решить вещи из первых принципов.)

Отметка времени в структуре "C" является битовым полем "C". Компилятор использует число после двоеточия для выделения количества битов в определении большего поля. В этом случае целое число без знака (4 байта). Смотрите здесь для лучшего объяснения. Большая проблема для битовых полей заключается в том, что биты назначаются на основе типа компьютера с прямым порядком байтов, поэтому они не очень переносимы.

Кажется, что в вашем объявлении формата Python есть ошибка. Вероятно, для даты должны быть выделены дополнительные 4-байтовые целые числа без знака. Что-то вроде:

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

Чтобы представить битовое поле в Python, используйте битовый массив Python для представления битов. Проверьте это.

Еще одна вещь, о которой следует помнить, это упаковка (1) на конструкции. Он говорит компилятору выравниваться по границам в один байт. Другими словами, не добавляйте отступы между полями. обычно выравнивание составляет 4 байта, в результате чего компилятор запускает каждое поле на границе 4 байта. Проверьте здесь для получения дополнительной информации.

Другие вопросы по тегам