blob: fa8084f4c6f9a90b3dc184782dd2cb6e50a0893b [file] [log] [blame]
#!/usr/bin/env python
from grammar import resd_header, data_block, data_block_sample_frequency, data_block_sample_arbitrary, data_block_header, data_block_subheader, data_block_metadata_item, BLOCK_TYPE, SAMPLE_TYPE
__VERSION__ = 1
class RESD:
def __init__(self, file_path):
self.file_handle = open(file_path, 'wb')
self.blocks = {}
self._write_header()
def __del__(self):
self.flush()
self.file_handle.close()
def new_block(self, sample_type, block_type, channel_id=0):
previous_block = self.get_block(sample_type, channel_id)
if previous_block is not None:
self.flush(sample_type, channel_id)
block = ({
BLOCK_TYPE.CONSTANT_FREQUENCY: RESDBlockConstantFrequency,
BLOCK_TYPE.ARBITRARY_TIMESTAMP: RESDBlockArbitraryTimestamp
})[block_type](sample_type, block_type, channel_id)
self.blocks[(sample_type, channel_id)] = block
return block
def get_block(self, sample_type, channel_id=0):
return self.blocks.get((sample_type, channel_id), None)
def get_block_or_create(self, sample_type, block_type, channel_id=0):
block = self.get_block(sample_type, channel_id)
return block if block else self.new_block(sample_type, block_type, channel_id)
def flush(self, sample_type=None, channel_id=None):
for key in list(self.blocks.keys()):
block_sample_type, block_channel_id = key
if sample_type and block_sample_type != sample_type:
continue
if channel_id and block_channel_id != channel_id:
continue
self.blocks[key].flush(self.file_handle)
del self.blocks[key]
def _write_header(self):
resd_header.build_stream({
'version': __VERSION__,
}, self.file_handle)
class RESDBlock:
def __init__(self, sample_type, block_type, channel_id):
self.sample_type = sample_type
self.block_type = block_type
self.channel_id = channel_id
self.block_metadata = RESDBlockMetadata()
self.samples = []
@property
def metadata(self):
return self.block_metadata
def flush(self, file):
metadata = self.metadata.build()
data_size = (
data_block_subheader.sizeof(header={'block_type': self.block_type}) +
metadata['size'] + 8 +
self._samples_sizeof()
)
header = self._header(data_size)
subheader = self._subheader()
data_block.build_stream({
'header': header,
'subheader': subheader,
'metadata': metadata,
'samples': self.samples,
}, file)
def _header(self, data_size):
return {
'block_type': self.block_type,
'sample_type': self.sample_type,
'channel_id': self.channel_id,
'data_size': data_size,
}
def _subheader(self):
return None
def _samples_sizeof(self):
pass
class RESDBlockConstantFrequency(RESDBlock):
__period = int(1e9)
__start_time = 0
@property
def period(self):
return self.__period
@period.setter
def period(self, value):
self.__period = value
@property
def frequency(self):
return 1e9 / self.__period
@frequency.setter
def frequency(self, value):
self.__period = int(1e9 / value)
@property
def start_time(self):
return self.__start_time
@start_time.setter
def start_time(self, value):
self.__start_time = value
def add_sample(self, sample):
self.samples.append({'sample': sample})
def _subheader(self):
return {
'start_time': self.__start_time,
'period': self.__period
}
def _samples_sizeof(self):
return sum(len(data_block_sample_frequency(self.sample_type).build(sample)) for sample in self.samples)
class RESDBlockArbitraryTimestamp(RESDBlock):
__start_time = 0
@property
def start_time(self):
return self.__start_time
@start_time.setter
def start_time(self, value):
self.__start_time = value
def add_sample(self, sample, timestamp):
self.samples.append({'sample': sample, 'timestamp': timestamp})
def _subheader(self):
return {
'start_time': self.__start_time,
}
def _samples_sizeof(self):
return sum(len(data_block_sample_arbitrary(self.sample_type).build(sample)) for sample in self.samples)
class RESDBlockMetadata:
def __init__(self):
self.metadata = []
self.keys = set()
def __getattr__(self, name):
prefix = 'insert_'
if name[:len(prefix)] != prefix:
return None
method = name[len(prefix):]
type_idx = ({
'int8': 0x00,
'uint8': 0x01,
'int16': 0x02,
'uint16': 0x03,
'int32': 0x04,
'uint32': 0x05,
'int64': 0x06,
'uint64': 0x07,
'float': 0x08,
'double': 0x09,
'text': 0x0A,
'blob': 0x0B,
}).get(method, None)
if method is None:
return None
return lambda key, value: self._insert(type_idx, key, value)
def build(self):
return {'items': self.metadata, 'size': self._sizeof()}
def remove(self, key):
if key not in self.keys:
return
self.keys.remove(key)
index = next(i for i, value in enumerate(self.metadata) if value['key'] == key)
self.metadata.pop(index)
def _sizeof(self):
return sum(len(data_block_metadata_item.build(item)) for item in self.metadata)
def _insert(self, type_idx, key, value):
self.remove(key)
self.keys.add(key)
self.metadata.push({
'type': type_idx,
'key': key,
'value': value
})