instrument: add get_raw() API
Derived metrics may be calculated form data in raw output that is not
present in the resulting MeasurementCSV. This adds a method to provide
uniform access to raw artifacts generated by an instrument.
diff --git a/devlib/instrument/__init__.py b/devlib/instrument/__init__.py
index 6d11eda..74113a3 100644
--- a/devlib/instrument/__init__.py
+++ b/devlib/instrument/__init__.py
@@ -297,3 +297,6 @@
def get_data(self, outfile):
pass
+
+ def get_raw(self):
+ return []
diff --git a/devlib/instrument/acmecape.py b/devlib/instrument/acmecape.py
index e1bb6c1..1053c9d 100644
--- a/devlib/instrument/acmecape.py
+++ b/devlib/instrument/acmecape.py
@@ -121,3 +121,6 @@
output_row.append(float(row[i])/1000)
writer.writerow(output_row)
return MeasurementsCsv(outfile, self.active_channels, self.sample_rate_hz)
+
+ def get_raw(self):
+ return [self.raw_data_file]
diff --git a/devlib/instrument/daq.py b/devlib/instrument/daq.py
index 75e854d..d497151 100644
--- a/devlib/instrument/daq.py
+++ b/devlib/instrument/daq.py
@@ -33,6 +33,7 @@
# pylint: disable=no-member
super(DaqInstrument, self).__init__(target)
self._need_reset = True
+ self._raw_files = []
if execute_command is None:
raise HostError('Could not import "daqpower": {}'.format(import_error_mesg))
if labels is None:
@@ -68,6 +69,7 @@
if not result.status == Status.OK: # pylint: disable=no-member
raise HostError(result.message)
self._need_reset = False
+ self._raw_files = []
def start(self):
if self._need_reset:
@@ -86,6 +88,7 @@
site = os.path.splitext(entry)[0]
path = os.path.join(tempdir, entry)
raw_file_map[site] = path
+ self._raw_files.append(path)
active_sites = unique([c.site for c in self.active_channels])
file_handles = []
@@ -131,6 +134,9 @@
for fh in file_handles:
fh.close()
+ def get_raw(self):
+ return self._raw_files
+
def teardown(self):
self.execute('close')
diff --git a/devlib/instrument/energy_probe.py b/devlib/instrument/energy_probe.py
index 5f47430..c8f179e 100644
--- a/devlib/instrument/energy_probe.py
+++ b/devlib/instrument/energy_probe.py
@@ -52,6 +52,7 @@
self.raw_output_directory = None
self.process = None
self.sample_rate_hz = 10000 # Determined empirically
+ self.raw_data_file = None
for label in self.labels:
for kind in self.attributes:
@@ -64,6 +65,7 @@
for i, rval in enumerate(self.resistor_values)]
rstring = ''.join(parts)
self.command = '{} -d {} -l {} {}'.format(self.caiman, self.device_entry, rstring, self.raw_output_directory)
+ self.raw_data_file = None
def start(self):
self.logger.debug(self.command)
@@ -92,10 +94,10 @@
num_of_ports = len(self.resistor_values)
struct_format = '{}I'.format(num_of_ports * self.attributes_per_sample)
not_a_full_row_seen = False
- raw_data_file = os.path.join(self.raw_output_directory, '0000000000')
+ self.raw_data_file = os.path.join(self.raw_output_directory, '0000000000')
- self.logger.debug('Parsing raw data file: {}'.format(raw_data_file))
- with open(raw_data_file, 'rb') as bfile:
+ self.logger.debug('Parsing raw data file: {}'.format(self.raw_data_file))
+ with open(self.raw_data_file, 'rb') as bfile:
with open(outfile, 'wb') as wfh:
writer = csv.writer(wfh)
writer.writerow(active_channels)
@@ -114,3 +116,6 @@
else:
not_a_full_row_seen = True
return MeasurementsCsv(outfile, self.active_channels, self.sample_rate_hz)
+
+ def get_raw(self):
+ return [self.raw_data_file]
diff --git a/devlib/instrument/frames.py b/devlib/instrument/frames.py
index d1899fb..54869c1 100644
--- a/devlib/instrument/frames.py
+++ b/devlib/instrument/frames.py
@@ -20,6 +20,7 @@
self.collector = None
self.header = None
self._need_reset = True
+ self._raw_file = None
self._init_channels()
def reset(self, sites=None, kinds=None, channels=None):
@@ -27,6 +28,7 @@
self.collector = self.collector_cls(self.target, self.period,
self.collector_target, self.header)
self._need_reset = False
+ self._raw_file = None
def start(self):
if self._need_reset:
@@ -38,14 +40,16 @@
self._need_reset = True
def get_data(self, outfile):
- raw_outfile = None
if self.keep_raw:
- raw_outfile = outfile + '.raw'
- self.collector.process_frames(raw_outfile)
+ self._raw_file = outfile + '.raw'
+ self.collector.process_frames(self._raw_file)
active_sites = [chan.label for chan in self.active_channels]
self.collector.write_frames(outfile, columns=active_sites)
return MeasurementsCsv(outfile, self.active_channels, self.sample_rate_hz)
+ def get_raw(self):
+ return [self._raw_file] if self._raw_file else []
+
def _init_channels(self):
raise NotImplementedError()
diff --git a/doc/instrumentation.rst b/doc/instrumentation.rst
index 6f381b4..f23fc3e 100644
--- a/doc/instrumentation.rst
+++ b/doc/instrumentation.rst
@@ -65,8 +65,8 @@
:INSTANTANEOUS: The instrument supports taking a single sample via
``take_measurement()``.
:CONTINUOUS: The instrument supports collecting measurements over a
- period of time via ``start()``, ``stop()``, and
- ``get_data()`` methods.
+ period of time via ``start()``, ``stop()``, ``get_data()``,
+ and (optionally) ``get_raw`` methods.
.. note:: It's possible for one instrument to support more than a single
mode.
@@ -161,6 +161,13 @@
.. note:: This method is only implemented by :class:`Instrument`\ s that
support ``CONTINUOUS`` measurement.
+.. method:: Instrument.get_raw()
+
+ Returns a list of paths to files containing raw output from the underlying
+ source(s) that is used to produce the data CSV. If now raw output is
+ generated or saved, an empty list will be returned. The format of the
+ contents of the raw files is entirely source-dependent.
+
.. attribute:: Instrument.sample_rate_hz
Sample rate of the instrument in Hz. Assumed to be the same for all channels.