Преобразование двоичной метки времени в строку
Я пытаюсь проанализировать проприетарный двоичный формат (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 байта. Проверьте здесь для получения дополнительной информации.