Files
controller-wisetopdataserver/python/biopro/generator.py
T
2021-12-20 14:52:55 +08:00

991 lines
31 KiB
Python

"""Artifact data generator.
cli options
-----------
::
./BioProController/RaspBerryPi3/python/biopro/generator.py [OPTIONS] [FILE]
OPTIONS
-D, --device DEVICE device ID
-L, --library PATH library search path
-T, --type TYPE output data format.
raw : raw data (default)
bprf : BioPro Recording File (output to file only)
-a, --amplitude VALUE signal amplitude, default 500
-c, --channel CH[,CH] channel list, default [0]
-g, --generator GENERATOR generator, could be:
zero : all zero
sin : sine save (default)
ramp : ramp
(Elite, Neulive serial)
TC4VAF2 Elite_Legacy
TDC4VAF2 Elite Neulive
(NeuliveSTI serial)
NeuliveSTI NeuliveSTI1.0 TDBC4VCTH
(EliteZM serial)
EliteZM I4V4Z4T4
(mode :IV, :FG, :CV, :ZT, :VT, :IT)
-h, --help print help document
-n, --noise VALUE noise factor, default 0
-p just list parameter, do not run anything
-s, --sample-rate RATE sampling rate in unit 1/s.
could be [N]K, default 100.
-t, --duration VALUE total time duration in unit second.
could be [N]h[N]m[N][.NNN]. also can use 'inf' to run forever. default 1.
OPTIONS (--type=bprf)
--bprf-verbose [NEWLINE] print what it write to stdout, default NEWLINE = \n
--split-size SIZE file split for every size, could be [N]K, [N]M, [N]G.
--split-time TIME file split for every second
ARGUMENTS
FILE output file, '-' output to stdout (default)
Class
-----
"""
import sys
from math import sin, pi
from random import random as _random
from typing import IO, Type
from biopro.devlib.data import I4V4Z4T4DataDecoder, EODInterrupt
from biopro.devlib.library import *
from biopro.util.cli import *
from biopro.util.text import part_prefix, part_suffix
from .recording import RecordingData, RecordingMetaFile, RecordingFileWriter
T = TypeVar('T')
def _sign(v: float) -> int:
return 1 if v > 0 else -1
RT_1 = Tuple[float, float, int, Optional[RecordingData]]
class Generator(metaclass=abc.ABCMeta):
"""abstract artifact data generator"""
__slots__ = ('device', 'configuration', 'channel', 'sample_rate', 'amplitude', 'noise',
'_start_time_stamp', '_prev_time_stamp')
def __init__(self, options: 'GeneratorOptions', configuration: Optional[DeviceConfiguration] = None):
# basic options
self.device = options.device
# addiction device configuration
self.configuration = configuration
self.channel = tuple(options.channel)
self.sample_rate = options.sample_rate
self.amplitude = options.amplitude
self.noise = options.noise
# internal data
self._start_time_stamp: Optional[float] = None
self._prev_time_stamp: Optional[float] = None
def data_should_generated(self, time_stamp: float, max_time_delta: float = None) -> RT_1:
"""calculate how many data should be generated according to the *sample_rate* and how
long did previous function be called.
:param time_stamp: current time stamp
:param max_time_delta: max time delta
:return: time stamp, time pass and the number of data should be generated.
"""
if self._start_time_stamp is None:
self._start_time_stamp = time_stamp
self._prev_time_stamp = time_stamp
return time_stamp, 0.0, 0, None
else:
delta = time_stamp - self._prev_time_stamp
if delta < 0:
return self._prev_time_stamp, 0.0, 0, None
if max_time_delta is not None and max_time_delta < delta:
delta = max_time_delta
count = int(delta * self.sample_rate)
if count == 0:
return time_stamp, 0.0, 0, None
elif count > 0xFFFF:
count = 0xFFF0
delta = count / self.sample_rate
data = self.new_data(int(1000 * (self._prev_time_stamp - self._start_time_stamp)))
else:
data = self.new_data(int(1000 * (self._prev_time_stamp - self._start_time_stamp)))
self._prev_time_stamp += delta
return self._prev_time_stamp, delta, count, data
def new_data(self, time_stamp: int) -> RecordingData:
return RecordingData(self.device, time_stamp, self.sample_rate)
@abc.abstractmethod
def get_data(self, time_stamp: float) -> List[RecordingData]:
""" generating data.
:param time_stamp: current time stamp in unit second
:return: list of (channel data)
"""
pass
def message(self) -> Optional[str]:
"""this method behavior as same as :func:`biopro.devlib.data.DataDecodeFormat.message`.
:return:
"""
return None
def factor_noise(self) -> float:
"""
:return: noise factor
"""
return 1 + self.noise * _random() - self.noise / 2
def new_meta_configuration(self) -> SimpleDeviceConfiguration:
return SimpleDeviceConfiguration({
DeviceConfiguration.LIBRARY: 'GENERATOR',
DeviceConfiguration.VERSION: '0.4.0',
DeviceConfiguration.SAMPLE_RATE: self.sample_rate,
DeviceConfiguration.CHANNEL: self.channel,
DeviceConfiguration.AMP_GAIN: 1,
})
class ZeroGenerator(Generator):
__slots__ = ()
def __init__(self, options: 'GeneratorOptions', configuration: Optional[DeviceConfiguration] = None):
super().__init__(options, configuration)
def get_data(self, time_stamp: float) -> List[RecordingData]:
if time_stamp < 0:
raise ValueError('negative time stamp value : ' + str(time_stamp))
ret = []
next_time_stamp = -1
while next_time_stamp < time_stamp:
next_time_stamp, time_pass, count, data = self.data_should_generated(time_stamp)
if data is not None:
for i in range(count):
channel_id = self.channel[i % len(self.channel)]
value = int(self.amplitude * self.factor_noise())
data.append_data(channel_id, value)
ret.append(data)
return ret
def new_meta_configuration(self) -> SimpleDeviceConfiguration:
ret = super().new_meta_configuration()
ret.set_parameter('MODE', 'zero')
return ret
class SinWaveGenerator(Generator):
"""Artifact sin ware data generator.
"""
TIME_CONSTANT = 'TIME_CONSTANT'
__slots__ = ('time_constant', '_phase')
def __init__(self, options: 'GeneratorOptions', configuration: Optional[DeviceConfiguration] = None):
super().__init__(options, configuration)
self.time_constant = 1
self._phase = 0.0
def get_data(self, time_stamp: float) -> List[RecordingData]:
if time_stamp < 0:
raise ValueError('negative time stamp value : ' + str(time_stamp))
ret = []
t = self.time_constant
channel_size = len(self.channel)
next_time_stamp = -1
while next_time_stamp < time_stamp:
next_time_stamp, time_pass, count, data = self.data_should_generated(time_stamp)
if data is not None:
for i in range(count):
channel_id = self.channel[i % channel_size]
value = int(self.amplitude * sin(self._phase / t * pi / 180))
data.append_data(channel_id, value)
self._phase += 1
ret.append(data)
return ret
def new_meta_configuration(self) -> SimpleDeviceConfiguration:
ret = super().new_meta_configuration()
ret.set_parameter('MODE', 'sin')
ret.set_parameter(self.TIME_CONSTANT, self.time_constant)
return ret
class RampGenerator(Generator):
"""Artifact ramp data generator"""
__slots__ = ('_count',)
def __init__(self, options: 'GeneratorOptions', configuration: Optional[DeviceConfiguration] = None):
super().__init__(options, configuration)
self._count = -self.amplitude
def get_data(self, time_stamp: float) -> List[RecordingData]:
if time_stamp < 0:
raise ValueError('negative time stamp value : ' + str(time_stamp))
ret = []
next_time_stamp = -1
while next_time_stamp < time_stamp:
next_time_stamp, time_pass, count, data = self.data_should_generated(time_stamp)
if data is not None:
amp = self.amplitude
cnt = self._count
for i in range(count):
channel_id = self.channel[i % len(self.channel)]
value = int(cnt * self.factor_noise())
data.append_data(channel_id, value)
cnt += 1
if cnt > amp:
cnt = -amp
self._count = cnt
ret.append(data)
return ret
def new_meta_configuration(self) -> SimpleDeviceConfiguration:
ret = super().new_meta_configuration()
ret.set_parameter('MODE', 'ramp')
return ret
class DeviceGenerator(Generator):
SUPPORT = {
'TC4VAF2': ('Elite_Legacy', '0.0.0'),
'Elite_Legacy': ('Elite_Legacy', '0.0.0'),
'TDC4VAF2': ('Elite', '0.1.0'),
'Elite': ('Elite', '0.1.0'),
'Neulive': ('Neulive', '1.2.0'),
}
__slots__ = ('_phase',)
def __init__(self, options: 'GeneratorOptions', configuration: DeviceConfiguration):
if configuration is None:
raise TypeError('NoneType configuration')
super().__init__(options, configuration)
self.amplitude = min(512.0, abs(self.amplitude))
self._phase = 0
def get_data(self, time_stamp: float) -> List[RecordingData]:
if time_stamp < 0:
raise ValueError('negative time stamp value : ' + str(time_stamp))
ret = []
time_step = 10 / self.sample_rate
nxt = -1 # next_time_stamp
while nxt < time_stamp:
nxt, time_pass, count, data = self.data_should_generated(time_stamp, time_step)
if data is not None and len(self.channel) != 0:
for i in range(count):
channel_id = self.channel[i % len(self.channel)]
value = int(self.amplitude * sin(self._phase * pi / 180) * self.factor_noise())
data.append_data(channel_id, value)
self._phase += 1
ret.append(data)
return ret
def new_meta_configuration(self) -> SimpleDeviceConfiguration:
conf = SimpleDeviceConfiguration(self.configuration)
conf.set_parameter(DeviceConfiguration.SAMPLE_RATE, self.sample_rate)
conf.set_parameter(DeviceConfiguration.CHANNEL, self.channel)
conf.set_parameter(DeviceConfiguration.AMP_GAIN, 1)
return conf
class NeuliveSTIGenerator(Generator):
SUPPORT = {
'NeuliveSTI': ('NeuliveSTI1.0', '1.0.0'),
'NeuliveSTI1.0': ('NeuliveSTI1.0', '1.0.0'),
'TDBC4VCTH': ('NeuliveSTI1.0', '1.0.0'),
}
__slots__ = ('voltage_drop_rate', '_voltage')
def __init__(self, options: 'GeneratorOptions', configuration: DeviceConfiguration):
if configuration is None:
raise TypeError('NoneType configuration')
super().__init__(options, configuration)
s = set(self.channel)
# NeuliveSTI only has 3 channels
s.intersection({0, 1, 2})
# force add zero channel (battery channel)
s.add(0)
self.channel = tuple(sorted(s))
# sample rate force to 1/s
self.sample_rate = 1
self.voltage_drop_rate = 10 # mv / sec
self._voltage = 4300 # mv
def get_data(self, time_stamp: float) -> List[RecordingData]:
if time_stamp < 0:
raise ValueError('negative time stamp value : ' + str(time_stamp))
ret = []
time_step = 1 # 1 second
nxt = -1 # next_time_stamp
while nxt < time_stamp:
nxt, _, count, data = self.data_should_generated(time_stamp, time_step)
if data is not None and len(self.channel) != 0:
for _ in range(count):
data.append_data(0, self._voltage)
if 1 in self.channel:
data.append_data(1, int(100 * self.factor_noise()))
if 2 in self.channel:
data.append_data(2, int(100 * self.factor_noise()))
self._voltage = int(self._voltage - self.sample_rate)
ret.append(data)
return ret
def new_data(self, time_stamp: int) -> RecordingData:
return RecordingData(self.device, time_stamp, 0)
def new_meta_configuration(self) -> SimpleDeviceConfiguration:
conf = SimpleDeviceConfiguration(self.configuration)
conf.set_parameter(DeviceConfiguration.SAMPLE_RATE, self.sample_rate)
conf.set_parameter(DeviceConfiguration.CHANNEL, self.channel)
conf.set_parameter(DeviceConfiguration.AMP_GAIN, 1)
return conf
class EliteZMGenerator(Generator):
MODE = (
"I-V Curve",
"Cyclic Voltammetry",
"Function Generator",
"R-T Curve",
"V-T Curve",
"I-T Curve",
)
VOLTAGE = tuple(map(lambda v: (v + 1) * 0x0001, range(65536)))
VOLTAGE_STEP = (30, 60, 90, 120)
STEP_TIME = (0.5, 1.0, 1.5, 2.0)
SUPPORT = {
'EliteZM': ('EliteZM', '1.2.30'),
'I4V4Z4T4': ('EliteZM', '1.2.30'),
}
__slots__ = ('_data_format', 'working_mode', 'voltage_origin', 'voltage_final', 'voltage_step',
'_voltage', '_voltage_step', '_cycle_number', '_phase', '_message', '_is_eod')
def __init__(self, options: 'GeneratorOptions', configuration: DeviceConfiguration):
if configuration is None:
raise TypeError('NoneType configuration')
super().__init__(options, configuration)
self.working_mode = configuration.get('MODE', 0)
if isinstance(self.working_mode, str):
self.working_mode = self.MODE.index(self.working_mode)
if self.working_mode == 0: # IV
self.channel = (0, 1)
elif self.working_mode == 1: # CV
self.channel = (0, 1)
elif self.working_mode == 2: # FG
self.channel = (1,)
elif self.working_mode == 3: # ZT
self.channel = (2,)
elif self.working_mode == 4: # VT
self.channel = (1,)
elif self.working_mode == 5: # IT
self.channel = (0,)
else:
raise ValueError('unsupported mode : ' + str(self.working_mode))
# sample rate force to 1/s
self.sample_rate = 1
self.voltage_origin = configuration.get('VOLT_ORIGIN', self.VOLTAGE[0]) # unit: V
self.voltage_final = configuration.get('VOLT_FINAL', self.VOLTAGE[-1]) # unit: V
self.voltage_step = configuration.get('VOLT_STEP', self.VOLTAGE_STEP[0]) # unit: mV
# internal data
self._voltage = self.voltage_origin * 1000
if self.voltage_origin <= self.voltage_final:
self._voltage_step = self.voltage_step
else:
self._voltage_step = -self.voltage_step
self._cycle_number = 0
self._phase = 0
self._message: Optional[str] = None
self._is_eod = False
def get_data(self, time_stamp: float) -> List[RecordingData]:
if time_stamp < 0:
raise ValueError('negative time stamp value : ' + str(time_stamp))
if self._is_eod:
raise EODInterrupt()
ret = []
nxt = -1 # next_time_stamp
while nxt < time_stamp:
nxt, data = self._gen_data(time_stamp)
if data is not None:
ret.append(data)
if self._is_eod:
break
return ret
def _gen_data(self, time_stamp: float) -> Tuple[float, Optional[RecordingData]]:
nxt, _, count, data = self.data_should_generated(time_stamp, 1)
if data is not None:
for _ in range(count):
current = 500 * (sin(self._phase * pi / 180) + 1.1)
# current
if 0 in self.channel:
data.append_data(0 + 3 * self._cycle_number, int(current))
# voltage
if 1 in self.channel:
data.append_data(1 + 3 * self._cycle_number, int(self._voltage))
# impedance
if 2 in self.channel:
if current == 0:
data.append_data(2 + 3 * self._cycle_number, 0)
else:
data.append_data(2 + 3 * self._cycle_number, int(self._voltage / current))
self._next_voltage()
if self._is_eod:
break
self._phase += 1
return nxt, data
def _next_voltage(self):
"""change voltage value. """
nxt_voltage = self._voltage + self._voltage_step
if nxt_voltage >= self.voltage_final * 1000:
self._voltage = self.voltage_final * 1000
self._voltage_step = -abs(self._voltage_step)
if self.working_mode == 0:
self._is_eod = True
elif self.working_mode == 1 and self.voltage_final < self.voltage_origin:
self._cycle_number += 1
self._message = I4V4Z4T4DataDecoder.MESSAGE_CYCLE_COMPLETE
elif nxt_voltage <= self.voltage_origin * 1000:
self._voltage = self.voltage_origin * 1000
self._voltage_step = abs(self._voltage_step)
if self.working_mode == 0:
self._is_eod = True
elif self.working_mode == 1 and self.voltage_origin < self.voltage_final:
self._cycle_number += 1
self._message = I4V4Z4T4DataDecoder.MESSAGE_CYCLE_COMPLETE
else:
self._voltage = nxt_voltage
def message(self) -> Optional[str]:
ret = self._message
self._message = None
return ret
def new_data(self, time_stamp: int) -> RecordingData:
return RecordingData(self.device, time_stamp, 0)
def new_meta_configuration(self) -> SimpleDeviceConfiguration:
conf = SimpleDeviceConfiguration(self.configuration)
conf.set_parameter(DeviceConfiguration.SAMPLE_RATE, self.sample_rate)
conf.set_parameter(DeviceConfiguration.CHANNEL, self.channel)
conf.set_parameter(DeviceConfiguration.AMP_GAIN, 1)
###
conf.set_parameter('MODE', self.MODE[self.working_mode])
conf.set_parameter('VOLT_ORIGIN', self.voltage_origin)
conf.set_parameter('VOLT_FINAL', self.voltage_final)
conf.set_parameter('VOLT_STEP', self.voltage_step)
conf.set_parameter('STEP_TIME', 1.0)
return conf
# noinspection PyUnusedLocal
class GeneratorOptions(CliOptions):
def __init__(self):
# general options
self.generator_type: str = 'sin'
self.generator_cls: Type[Generator] = SinWaveGenerator
self.configuration: Optional[DeviceConfiguration] = None
self.device: int = 0
self.channel: List[int] = [0]
self.sample_rate = 100
self.amplitude = 500
self.noise = 0.0
# library path
self.repository = DeviceLibraryRepository()
@cli_options('-g', '--generator', value='GENERATOR')
def _generator_type(self, opt: str, value: str):
"""generator, could be:
zero : all zero
sin : sine save (default)
ramp : ramp
(Elite, Neulive serial)
TC4VAF2 Elite_Legacy
TDC4VAF2 Elite Neulive
(NeuliveSTI serial)
NeuliveSTI NeuliveSTI1.0 TDBC4VCTH
(EliteZM serial)
EliteZM I4V4Z4T4
(mode :IV, :FG, :CV, :ZT, :VT, :IT)
"""
self.set_generator(value)
def set_generator(self, generator_type: str):
self.generator_type = generator_type
if generator_type == 'zero':
self.generator_cls = ZeroGenerator
self.configuration = None
elif generator_type == 'sin':
self.generator_cls = SinWaveGenerator
self.configuration = None
elif generator_type == 'ramp':
self.generator_cls = RampGenerator
self.configuration = None
else:
try:
lib, ver = DeviceGenerator.SUPPORT[generator_type]
self.configuration = self._new_device_configuration(lib, ver)
self.generator_cls = DeviceGenerator
return
except KeyError:
pass
try:
lib, ver = NeuliveSTIGenerator.SUPPORT[generator_type]
self.configuration = self._new_device_configuration(lib, ver)
self.generator_cls = NeuliveSTIGenerator
return
except KeyError:
pass
try:
lib, ver = EliteZMGenerator.SUPPORT[generator_type]
self.configuration = self._new_device_configuration(lib, ver)
self.generator_cls = EliteZMGenerator
# init ZM options
self.configuration.set_parameter('VOLT_ORIGIN', EliteZMGenerator.VOLTAGE[0])
self.configuration.set_parameter('VOLT_FINAL', EliteZMGenerator.VOLTAGE[-1])
return
except KeyError:
pass
raise RuntimeError('unknown generator type : ' + generator_type)
@cli_options('-D', '--device', value='DEVICE')
def _device(self, opt: str, value: str):
"""device ID"""
self.device = int(value)
@cli_options('-c', '--channel', value='CH[,CH]')
def _channel(self, opt: str, value: str):
"""channel list, default [0]"""
self.channel = list(map(int, value.split(',')))
@cli_options('-s', '--sample-rate', value='RATE')
def _sample_rate(self, opt: str, value: str):
"""sampling rate in unit 1/s.
could be [N]K, default 100.
"""
if value.endswith('K'):
self.sample_rate = int(value[:-1]) * 1000
else:
self.sample_rate = int(value)
@cli_options('-a', '--amplitude', value='VALUE')
def _amplitude(self, opt: str, value: str):
"""signal amplitude, default 500"""
self.amplitude = int(value)
@cli_options('-n', '--noise', value='VALUE')
def _noise(self, opt: str, value: str):
"""noise factor, default 0"""
self.noise = float(value)
@cli_options('-L', '--library', value='PATH')
def _library_path(self, opt: str, value: str):
"""library search path"""
self.repository.add_library_path(value)
def get_generator(self) -> Generator:
return self.generator_cls(self, self.configuration)
def _new_device_configuration(self, library_name: str, library_version: str) -> DeviceConfiguration:
library = self.repository.get_library(library_name, library_version)
if library is None:
raise RuntimeError('library ' + library_name + ' ' + library_version + ' not found')
parameter = DeviceParameter(library)
return DeviceParameterConfiguration(library, parameter)
# noinspection PyUnusedLocal
class Main(CliMain, GeneratorOptions):
def __init__(self):
super().__init__()
GeneratorOptions.__init__(self)
# internal property
self._generator: Generator = None
# output
self.output_type = 'raw'
self.output_file = '-'
# time
self.duration: Optional[float] = 1.0
'''time duration in unit second'''
self.list_parameter = False
# recording file writer options
self.writer_split_time: Optional[int] = None
self.writer_split_size: Optional[int] = None
self.writer_print_data = None
@cli_flags('-h', '--help', force_return=True)
def _help(self, opt: str):
"""print help document"""
self.print_help()
def set_generator(self, generator_type: str):
super().set_generator(generator_type)
c = self.configuration
if c is None:
return
m: Dict[str, str] = {}
h: List[CliHandleOption] = []
for k in c.keys():
if k.startswith('_'):
continue
if k in (DeviceConfiguration.CHANNEL, DeviceConfiguration.SAMPLE_RATE):
# do not include channel and sample_rate options
continue
o = '--' + k.lower().replace('_', '-')
m[o] = k
h.append(CliHandleOption(o, help_doc=k))
if len(h) > 0:
def _callback(flag: str, value: Optional[str]):
c.set_parameter(m[flag], value)
self.extend_options_callback(_callback, *h,
help_section='Configuration for ' + c.library)
@cli_options('-T', '--type', value='TYPE')
def _output_type(self, opt: str, value: str):
"""output data format.
raw : raw data (default)
bprf : BioPro Recording File (output to file only)
"""
self.output_type = value
@cli_options('-t', '--duration', value='VALUE')
def _duration(self, opt: str, value: str):
"""total time duration in unit second.
could be [N]h[N]m[N][.NNN]. also can use 'inf' to run forever. default 1.
"""
if value == 'inf':
self.duration = None
else:
self.duration = self._parse_time(value)
@cli_flags('-p')
def _list_parameter(self, flag: str):
"""just list parameter, do not run anything"""
self.list_parameter = True
@cli_arguments('?', value='FILE')
def _file(self, pos: int, value: str):
"""output file, '-' output to stdout (default)"""
self.output_file = value
@cli_options('--split-time', value='TIME')
@cli_help_section('OPTIONS (--type=bprf)')
def _split_time(self, opt: str, value: str):
"""file split for every second"""
if self.output_type != 'bprf':
print('WARN', opt + ' only work for --type=bprf')
self.writer_split_time = int(value)
@cli_options('--split-size', value='SIZE')
@cli_help_section('OPTIONS (--type=bprf)')
def _split_size(self, opt: str, value: str):
"""file split for every size, could be [N]K, [N]M, [N]G."""
if self.output_type != 'bprf':
print('WARN', opt + ' only work for --type=bprf')
self.writer_split_size = self._parse_size(value)
@cli_options('--bprf-verbose', value='NEWLINE', optional=True)
@cli_help_section('OPTIONS (--type=bprf)')
def _bprf_verbose(self, opt: str, value: str):
"""print what it write to stdout, default NEWLINE = \\n"""
if self.output_type != 'bprf':
print('WARN', opt + ' only work for --type=bprf')
if value is None:
self.writer_print_data = '\n'
else:
self.writer_print_data = value
@staticmethod
def _parse_size(expr: str) -> int:
factor = 1
if expr.endswith('K'):
factor = 1000
expr = expr[:-1]
elif expr.endswith('M'):
factor = 1000 * 1000
expr = expr[:-1]
elif expr.endswith('G'):
factor = 1000 * 1000 * 1000
expr = expr[:-1]
return int(expr) * factor
@staticmethod
def _parse_time(expr: str) -> float:
"""parse time expression
:param expr: expression in [N]h[N]m[N][.NNN]
:return: second
"""
t_h, expr = part_prefix(expr, 'h', missing=0)
t_h = int(t_h)
if len(expr) > 0:
t_m, expr = part_prefix(expr, 'm', missing=0)
t_m = int(t_m)
else:
t_m = 0
if len(expr) > 0:
t_s, t_u = part_suffix(expr, '.')
t_s = int(t_s)
if t_u is not None:
t_u = t_u[:3]
if len(t_u) < 3:
t_u += '0'
t_u = int(t_u)
else:
t_u = 0
else:
t_s = t_u = 0
return t_u * 1e-3 + t_s + t_m * 60 + t_h * 60 * 60
def run(self):
self._generator = self.get_generator()
if self.list_parameter:
configuration = self._generator.configuration
for para in configuration.keys():
print(para)
else:
if self.output_type == 'raw':
if self.output_file == '-':
self._run(sys.stdout)
else:
with open(self.output_file, 'w') as output:
self._run(output)
elif self.output_type == 'bprf':
if self.output_file == '-':
raise RuntimeError('bprf cannot output to stdout')
meta = RecordingMetaFile(self.output_file)
meta.configuration = self._generator.new_meta_configuration()
with RecordingFileWriter(meta) as output:
output.splitting_threshold_size = self.writer_split_size
output.splitting_threshold_time = self.writer_split_time
self._run(output)
else:
raise RuntimeError('unknown output type : ' + self.output_type)
def _run(self, output: Union[IO, RecordingFileWriter]):
if self.duration is None:
raise RuntimeError()
is_writer = isinstance(output, RecordingFileWriter)
# init first time
self._generator.get_data(0.0)
try:
if self.writer_print_data or not is_writer:
for data in self._generator.get_data(self.duration):
if len(data) > 0:
if is_writer:
output.write(data)
for time_stamp, channel_id, value in data.entry_iter():
print('%.4f' % time_stamp, channel_id, value, end=self.writer_print_data)
else:
for data in self._generator.get_data(self.duration):
if len(data) > 0:
output.write(data)
except EODInterrupt:
pass
if __name__ == '__main__':
Main().main()