from enum import IntEnum from pathlib import Path from typing import Optional, Union, List, Tuple, Any # noinspection PyUnresolvedReferences import biopro.ext.message as util_message from biopro.recording import RecordingMetaFile, RecordingData from biopro.util.channel import ChannelMask __all__ = ['DataPresentMode', 'DataMessageEncoder', 'RecordingFileReader'] inf = float('inf') class DataPresentMode(IntEnum): RAW = 0 FIRST = 1 AVERAGE = 2 # XXX not supported current AVERAGE_BOUNDARY = 3 class DataMessageEncoder: """It is typing used of the abstract interface of DataMessageEncoder defined in c extension. :: | | 1 | 2 | 3 | 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 ----------------------------------------------------------------- | header | device ID | sample rate | | ch_count | | channel | width | mode | data length | | time stamp | | ------------------------------------------------------------- | | index | data[*] | mode == FIRST|AVERAGE | ------------------------------------------------------------- | | index | data[*] | mode == AVERAGE_BOUNDARY | min % | max % | | ------------------------------------------------------------- | | channel' | width | mode | data length | next data section | ------------------------------------------------------------- | | header=0x03 | device' ID | sample rate' | next device section | ------------------------------------------------------------- | header message header, value > 0 data length little endian time stamp msec, little endian sample rate unit 1/s, little endian ch_count channel count width data width, 2 or 4 bytes mode data present mode index time_step. always increase. may overflow data unsigned little endian int. actually value equal to data - 0x8000 """ __slots__ = '__impl', '__mod_count', '__mod_message', '__message_cache' def __init__(self, header: int, mode: DataPresentMode, sample_rate: int, device: int, channel: Union[int, List[int], ChannelMask]): """ :param header: data message header :param mode: data present mode :param sample_rate: sample rate in unit 1/sec :param device: listen device ID :param channel: listen channel or channel mask :return: data message encoder """ if sample_rate < 1: raise ValueError('sample rate less then 1 : ' + str(sample_rate)) if isinstance(channel, int): channel = [channel] self.__impl: 'DataMessageEncoder' = util_message.DataMessageEncoder(header, mode.value, sample_rate, device, channel) # message() cache self.__mod_count = 0 self.__mod_message = 0 self.__message_cache: Optional[bytes] = None @property def header(self) -> int: return self.__impl.header @property def width(self) -> int: return self.__impl.width @property def mode(self) -> int: return self.__impl.mode @mode.setter def mode(self, value: int): self.__impl.mode = value @property def sample_rate(self) -> float: return self.__impl.sample_rate @property def device(self) -> int: return self.__impl.device def channels(self) -> List[int]: return self.__impl.channels() def contain_channel(self, channel: int) -> bool: return self.__impl.contain_channel(channel) def append_entry(self, time_stamp: float, channel: int, value: int): """ :param time_stamp: time stamp in unit msec :param channel: channel ID :param value: data value """ self.__impl.append_entry(time_stamp, channel, value) self.__mod_count += 1 def append_data(self, data: RecordingData, time_start: float = 0.0, time_stop: float = inf): self.__impl.append_data(data, time_start, time_stop) self.__mod_count += 1 def append_file(self, file: 'RecordingFileReader', time_start: float = 0.0, time_stop: float = inf): self.__impl.append_file(file._impl, time_start, time_stop) self.__mod_count += 1 def message(self) -> Optional[bytes]: if self.__message_cache is not None: if self.__mod_message == self.__mod_count: return self.__message_cache ret = self.__impl.message() self.__message_cache = ret self.__mod_message = self.__mod_count return ret class RecordingFileReader: """It is typing used of the warp class for RecordingFileReader defined in c extension. """ __slots__ = ('_impl',) def __init__(self, meta: Union[RecordingMetaFile, Path, str]): if isinstance(meta, RecordingMetaFile): path = str(meta.filepath) else: path = str(meta) self._impl: 'RecordingFileReader' = util_message.RecordingFileReader(path.encode()) @property def file_path(self) -> bytes: return self._impl.file_path @property def version_number(self) -> int: return self._impl.version_number @property def create_time(self) -> int: return self._impl.create_time @property def file_uuid(self) -> bytes: return self._impl.file_uuid @property def data_format(self) -> bytes: return self._impl.data_format @property def device_name(self) -> bytes: return self._impl.device_name @property def device_address(self) -> bytes: return self._impl.device_address @property def device_serial(self) -> bytes: return self._impl.device_serial @property def parameter_count(self) -> int: return self._impl.parameter_count def parameter(self, index: int) -> Tuple[str, Any]: return self._impl.parameter(index) @property def recording_file_size(self) -> int: return self._impl.recording_file_size def recording_file_name(self, file_index: int) -> bytes: return self._impl.recording_file_name(file_index) def invalid_cache(self): # TODO not implement in C extension pass