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.