Instrument/Acmecape: Add support for acmecape
diff --git a/devlib/instrument/acmecape.py b/devlib/instrument/acmecape.py
new file mode 100644
index 0000000..abf6a19
--- /dev/null
+++ b/devlib/instrument/acmecape.py
@@ -0,0 +1,122 @@
+#pylint: disable=attribute-defined-outside-init
+from __future__ import division
+import csv
+import os
+import time
+import tempfile
+from fcntl import fcntl, F_GETFL, F_SETFL
+from string import Template
+from subprocess import Popen, PIPE, STDOUT
+
+from devlib import Instrument, CONTINUOUS, MeasurementsCsv
+from devlib.exception import HostError
+from devlib.utils.misc import which
+
+OUTPUT_CAPTURE_FILE = 'acme-cape.csv'
+IIOCAP_CMD_TEMPLATE = Template("""
+${iio_capture} -n ${host} -b ${buffer_size} -c -f ${outfile} ${iio_device}
+""")
+
+def _read_nonblock(pipe, size=1024):
+    fd = pipe.fileno()
+    flags = fcntl(fd, F_GETFL)
+    flags |= os.O_NONBLOCK
+    fcntl(fd, F_SETFL, flags)
+
+    output = ''
+    try:
+        while True:
+            output += pipe.read(size)
+    except IOError:
+        pass
+    return output
+
+
+class AcmeCapeInstrument(Instrument):
+
+    mode = CONTINUOUS
+
+    def __init__(self, target,
+                 iio_capture=which('iio_capture'),
+                 host='baylibre-acme.local',
+                 iio_device='iio:device0',
+                 buffer_size=256):
+        super(AcmeCapeInstrument, self).__init__(target)
+        self.iio_capture = iio_capture
+        self.host = host
+        self.iio_device = iio_device
+        self.buffer_size = buffer_size
+        if self.iio_capture is None:
+            raise HostError('Missing iio-capture binary')
+        self.command = None
+        self.process = None
+
+        self.add_channel('shunt', 'voltage')
+        self.add_channel('bus', 'voltage')
+        self.add_channel('device', 'power')
+        self.add_channel('device', 'current')
+        self.add_channel('timestamp', 'time_ms')
+
+    def reset(self, sites=None, kinds=None, channels=None):
+        super(AcmeCapeInstrument, self).reset(sites, kinds, channels)
+        self.raw_data_file = tempfile.mkstemp('.csv')[1]
+        params = dict(
+            iio_capture=self.iio_capture,
+            host=self.host,
+            buffer_size=self.buffer_size,
+            iio_device=self.iio_device,
+            outfile=self.raw_data_file
+        )
+        self.command = IIOCAP_CMD_TEMPLATE.substitute(**params)
+        self.logger.debug('ACME cape command: {}'.format(self.command))
+
+    def start(self):
+        self.process = Popen(self.command.split(), stdout=PIPE, stderr=STDOUT)
+
+    def stop(self):
+        self.process.terminate()
+        timeout_secs = 10
+        for _ in xrange(timeout_secs):
+            if self.process.poll() is not None:
+                break
+            time.sleep(1)
+        else:
+            output = _read_nonblock(self.process.stdout)
+            self.process.kill()
+            self.logger.error('iio-capture did not terminate gracefully')
+            if self.process.poll() is None:
+                msg = 'Could not terminate iio-capture:\n{}'
+                raise HostError(msg.format(output))
+        if not os.path.isfile(self.raw_data_file):
+            raise HostError('Output CSV not generated.')
+
+    def get_data(self, outfile):
+        if os.stat(self.raw_data_file).st_size == 0:
+            self.logger.warning('"{}" appears to be empty'.format(self.raw_data_file))
+            return
+
+        all_channels = [c.label for c in self.list_channels()]
+        active_channels = [c.label for c in self.active_channels]
+        active_indexes = [all_channels.index(ac) for ac in active_channels]
+
+        with open(self.raw_data_file, 'rb') as fh:
+            with open(outfile, 'wb') as wfh:
+                writer = csv.writer(wfh)
+                writer.writerow(active_channels)
+
+                reader = csv.reader(fh, skipinitialspace=True)
+                header = reader.next()
+                ts_index = header.index('timestamp ms')
+
+
+                for row in reader:
+                    output_row = []
+                    for i in active_indexes:
+                        if i == ts_index:
+                            # Leave time in ms
+                            output_row.append(float(row[i]))
+                        else:
+                            # Convert rest into standard units.
+                            output_row.append(float(row[i])/1000)
+                    writer.writerow(output_row)
+        return MeasurementsCsv(outfile, self.active_channels)